home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / feedparser.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2008-10-13  |  86.9 KB  |  3,107 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. __version__ = '4.1'
  5. __license__ = "Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  6. __author__ = 'Mark Pilgrim <http://diveintomark.org/>'
  7. __contributors__ = [
  8.     'Jason Diamond <http://injektilo.org/>',
  9.     'John Beimler <http://john.beimler.org/>',
  10.     'Fazal Majid <http://www.majid.info/mylos/weblog/>',
  11.     'Aaron Swartz <http://aaronsw.com/>',
  12.     'Kevin Marks <http://epeus.blogspot.com/>']
  13. _debug = 0
  14. USER_AGENT = 'UniversalFeedParser/%s +http://feedparser.org/' % __version__
  15. ACCEPT_HEADER = 'application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1'
  16. PREFERRED_XML_PARSERS = [
  17.     'drv_libxml2']
  18. TIDY_MARKUP = 0
  19. PREFERRED_TIDY_INTERFACES = [
  20.     'uTidy',
  21.     'mxTidy']
  22. import sgmllib
  23. import re
  24. import sys
  25. import copy
  26. import urlparse
  27. import time
  28. import rfc822
  29. import types
  30. import cgi
  31. import urllib
  32. import urllib2
  33.  
  34. try:
  35.     from cStringIO import StringIO as _StringIO
  36. except:
  37.     from StringIO import StringIO as _StringIO
  38.  
  39.  
  40. try:
  41.     import gzip
  42. except:
  43.     gzip = None
  44.  
  45.  
  46. try:
  47.     import zlib
  48. except:
  49.     zlib = None
  50.  
  51.  
  52. try:
  53.     import xml.sax as xml
  54.     xml.sax.make_parser(PREFERRED_XML_PARSERS)
  55.     from xml.sax.saxutils import escape as _xmlescape
  56.     _XML_AVAILABLE = 1
  57. except:
  58.     _XML_AVAILABLE = 0
  59.     
  60.     def _xmlescape(data):
  61.         data = data.replace('&', '&')
  62.         data = data.replace('>', '>')
  63.         data = data.replace('<', '<')
  64.         return data
  65.  
  66.  
  67.  
  68. try:
  69.     import base64
  70.     import binascii
  71. except:
  72.     base64 = binascii = None
  73.  
  74.  
  75. try:
  76.     import cjkcodecs.aliases as cjkcodecs
  77. except:
  78.     pass
  79.  
  80.  
  81. try:
  82.     import iconv_codec
  83. except:
  84.     pass
  85.  
  86.  
  87. try:
  88.     import chardet
  89.     if _debug:
  90.         import chardet.constants as chardet
  91.         chardet.constants._debug = 1
  92. except:
  93.     chardet = None
  94.  
  95.  
  96. class ThingsNobodyCaresAboutButMe(Exception):
  97.     pass
  98.  
  99.  
  100. class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe):
  101.     pass
  102.  
  103.  
  104. class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe):
  105.     pass
  106.  
  107.  
  108. class NonXMLContentType(ThingsNobodyCaresAboutButMe):
  109.     pass
  110.  
  111.  
  112. class UndeclaredNamespace(Exception):
  113.     pass
  114.  
  115. sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
  116. sgmllib.special = re.compile('<!')
  117. sgmllib.charref = re.compile('&#(x?[0-9A-Fa-f]+)[^0-9A-Fa-f]')
  118. SUPPORTED_VERSIONS = {
  119.     '': 'unknown',
  120.     'rss090': 'RSS 0.90',
  121.     'rss091n': 'RSS 0.91 (Netscape)',
  122.     'rss091u': 'RSS 0.91 (Userland)',
  123.     'rss092': 'RSS 0.92',
  124.     'rss093': 'RSS 0.93',
  125.     'rss094': 'RSS 0.94',
  126.     'rss20': 'RSS 2.0',
  127.     'rss10': 'RSS 1.0',
  128.     'rss': 'RSS (unknown version)',
  129.     'atom01': 'Atom 0.1',
  130.     'atom02': 'Atom 0.2',
  131.     'atom03': 'Atom 0.3',
  132.     'atom10': 'Atom 1.0',
  133.     'atom': 'Atom (unknown version)',
  134.     'cdf': 'CDF',
  135.     'hotrss': 'Hot RSS' }
  136.  
  137. try:
  138.     UserDict = dict
  139. except NameError:
  140.     from UserDict import UserDict
  141.     
  142.     def dict(aList):
  143.         rc = { }
  144.         for k, v in aList:
  145.             rc[k] = v
  146.         
  147.         return rc
  148.  
  149.  
  150.  
  151. class FeedParserDict(UserDict):
  152.     keymap = {
  153.         'channel': 'feed',
  154.         'items': 'entries',
  155.         'guid': 'id',
  156.         'date': 'updated',
  157.         'date_parsed': 'updated_parsed',
  158.         'description': [
  159.             'subtitle',
  160.             'summary'],
  161.         'url': [
  162.             'href'],
  163.         'modified': 'updated',
  164.         'modified_parsed': 'updated_parsed',
  165.         'issued': 'published',
  166.         'issued_parsed': 'published_parsed',
  167.         'copyright': 'rights',
  168.         'copyright_detail': 'rights_detail',
  169.         'tagline': 'subtitle',
  170.         'tagline_detail': 'subtitle_detail' }
  171.     
  172.     def __getitem__(self, key):
  173.         if key == 'category':
  174.             return UserDict.__getitem__(self, 'tags')[0]['term']
  175.         
  176.         realkey = self.keymap.get(key, key)
  177.         if type(realkey) == types.ListType:
  178.             for k in realkey:
  179.                 if UserDict.has_key(self, k):
  180.                     return UserDict.__getitem__(self, k)
  181.                     continue
  182.                 None if key == 'categories' else []
  183.             
  184.         
  185.         if UserDict.has_key(self, key):
  186.             return UserDict.__getitem__(self, key)
  187.         
  188.         return UserDict.__getitem__(self, realkey)
  189.  
  190.     
  191.     def __setitem__(self, key, value):
  192.         for k in self.keymap.keys():
  193.             if key == k:
  194.                 key = self.keymap[k]
  195.                 if type(key) == types.ListType:
  196.                     key = key[0]
  197.                 
  198.             type(key) == types.ListType
  199.         
  200.         return UserDict.__setitem__(self, key, value)
  201.  
  202.     
  203.     def get(self, key, default = None):
  204.         if self.has_key(key):
  205.             return self[key]
  206.         else:
  207.             return default
  208.  
  209.     
  210.     def setdefault(self, key, value):
  211.         if not self.has_key(key):
  212.             self[key] = value
  213.         
  214.         return self[key]
  215.  
  216.     
  217.     def has_key(self, key):
  218.         
  219.         try:
  220.             if not hasattr(self, key):
  221.                 pass
  222.             return UserDict.has_key(self, key)
  223.         except AttributeError:
  224.             return False
  225.  
  226.  
  227.     
  228.     def __getattr__(self, key):
  229.         
  230.         try:
  231.             return self.__dict__[key]
  232.         except KeyError:
  233.             pass
  234.  
  235.         
  236.         try:
  237.             return self.__getitem__(key)
  238.         except:
  239.             raise AttributeError, "object has no attribute '%s'" % key
  240.  
  241.  
  242.     
  243.     def __setattr__(self, key, value):
  244.         if key.startswith('_') or key == 'data':
  245.             self.__dict__[key] = value
  246.         else:
  247.             return self.__setitem__(key, value)
  248.  
  249.     
  250.     def __contains__(self, key):
  251.         return self.has_key(key)
  252.  
  253.  
  254.  
  255. def zopeCompatibilityHack():
  256.     global FeedParserDict, FeedParserDict
  257.     del FeedParserDict
  258.     
  259.     def FeedParserDict(aDict = None):
  260.         rc = { }
  261.         if aDict:
  262.             rc.update(aDict)
  263.         
  264.         return rc
  265.  
  266.  
  267. _ebcdic_to_ascii_map = None
  268.  
  269. def _ebcdic_to_ascii(s):
  270.     global _ebcdic_to_ascii_map
  271.     if not _ebcdic_to_ascii_map:
  272.         emap = (0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15, 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31, 128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7, 144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26, 32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33, 38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94, 45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63, 186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34, 195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200, 201, 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205, 206, 207, 208, 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 123, 65, 66, 67, 68, 69, 70, 71, 72, 73, 232, 233, 234, 235, 236, 237, 125, 74, 75, 76, 77, 78, 79, 80, 81, 82, 238, 239, 240, 241, 242, 243, 92, 159, 83, 84, 85, 86, 87, 88, 89, 90, 244, 245, 246, 247, 248, 249, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250, 251, 252, 253, 254, 255)
  273.         import string
  274.         _ebcdic_to_ascii_map = string.maketrans(''.join(map(chr, range(256))), ''.join(map(chr, emap)))
  275.     
  276.     return s.translate(_ebcdic_to_ascii_map)
  277.  
  278. _urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
  279.  
  280. def _urljoin(base, uri):
  281.     uri = _urifixer.sub('\\1\\3', uri)
  282.     return urlparse.urljoin(base, uri)
  283.  
  284.  
  285. class _FeedParserMixin:
  286.     namespaces = {
  287.         '': '',
  288.         'http://backend.userland.com/rss': '',
  289.         'http://blogs.law.harvard.edu/tech/rss': '',
  290.         'http://purl.org/rss/1.0/': '',
  291.         'http://my.netscape.com/rdf/simple/0.9/': '',
  292.         'http://example.com/newformat#': '',
  293.         'http://example.com/necho': '',
  294.         'http://purl.org/echo/': '',
  295.         'uri/of/echo/namespace#': '',
  296.         'http://purl.org/pie/': '',
  297.         'http://purl.org/atom/ns#': '',
  298.         'http://www.w3.org/2005/Atom': '',
  299.         'http://purl.org/rss/1.0/modules/rss091#': '',
  300.         'http://webns.net/mvcb/': 'admin',
  301.         'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
  302.         'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
  303.         'http://media.tangent.org/rss/1.0/': 'audio',
  304.         'http://backend.userland.com/blogChannelModule': 'blogChannel',
  305.         'http://web.resource.org/cc/': 'cc',
  306.         'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
  307.         'http://purl.org/rss/1.0/modules/company': 'co',
  308.         'http://purl.org/rss/1.0/modules/content/': 'content',
  309.         'http://my.theinfo.org/changed/1.0/rss/': 'cp',
  310.         'http://purl.org/dc/elements/1.1/': 'dc',
  311.         'http://purl.org/dc/terms/': 'dcterms',
  312.         'http://purl.org/rss/1.0/modules/email/': 'email',
  313.         'http://purl.org/rss/1.0/modules/event/': 'ev',
  314.         'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
  315.         'http://freshmeat.net/rss/fm/': 'fm',
  316.         'http://xmlns.com/foaf/0.1/': 'foaf',
  317.         'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
  318.         'http://postneo.com/icbm/': 'icbm',
  319.         'http://purl.org/rss/1.0/modules/image/': 'image',
  320.         'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
  321.         'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
  322.         'http://purl.org/rss/1.0/modules/link/': 'l',
  323.         'http://search.yahoo.com/mrss': 'media',
  324.         'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
  325.         'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
  326.         'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
  327.         'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
  328.         'http://purl.org/rss/1.0/modules/reference/': 'ref',
  329.         'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
  330.         'http://purl.org/rss/1.0/modules/search/': 'search',
  331.         'http://purl.org/rss/1.0/modules/slash/': 'slash',
  332.         'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
  333.         'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
  334.         'http://hacks.benhammersley.com/rss/streaming/': 'str',
  335.         'http://purl.org/rss/1.0/modules/subscription/': 'sub',
  336.         'http://purl.org/rss/1.0/modules/syndication/': 'sy',
  337.         'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
  338.         'http://purl.org/rss/1.0/modules/threading/': 'thr',
  339.         'http://purl.org/rss/1.0/modules/textinput/': 'ti',
  340.         'http://madskills.com/public/xml/rss/module/trackback/': 'trackback',
  341.         'http://wellformedweb.org/commentAPI/': 'wfw',
  342.         'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
  343.         'http://www.w3.org/1999/xhtml': 'xhtml',
  344.         'http://www.w3.org/XML/1998/namespace': 'xml',
  345.         'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf' }
  346.     _matchnamespaces = { }
  347.     can_be_relative_uri = [
  348.         'link',
  349.         'id',
  350.         'wfw_comment',
  351.         'wfw_commentrss',
  352.         'docs',
  353.         'url',
  354.         'href',
  355.         'comments',
  356.         'license',
  357.         'icon',
  358.         'logo']
  359.     can_contain_relative_uris = [
  360.         'content',
  361.         'title',
  362.         'summary',
  363.         'info',
  364.         'tagline',
  365.         'subtitle',
  366.         'copyright',
  367.         'rights',
  368.         'description']
  369.     can_contain_dangerous_markup = [
  370.         'content',
  371.         'title',
  372.         'summary',
  373.         'info',
  374.         'tagline',
  375.         'subtitle',
  376.         'copyright',
  377.         'rights',
  378.         'description']
  379.     html_types = [
  380.         'text/html',
  381.         'application/xhtml+xml']
  382.     
  383.     def __init__(self, baseuri = None, baselang = None, encoding = 'utf-8'):
  384.         if _debug:
  385.             sys.stderr.write('initializing FeedParser\n')
  386.         
  387.         if not self._matchnamespaces:
  388.             for k, v in self.namespaces.items():
  389.                 self._matchnamespaces[k.lower()] = v
  390.             
  391.         
  392.         self.feeddata = FeedParserDict()
  393.         self.encoding = encoding
  394.         self.entries = []
  395.         self.version = ''
  396.         self.namespacesInUse = { }
  397.         self.infeed = 0
  398.         self.inentry = 0
  399.         self.incontent = 0
  400.         self.intextinput = 0
  401.         self.inimage = 0
  402.         self.inauthor = 0
  403.         self.incontributor = 0
  404.         self.inpublisher = 0
  405.         self.insource = 0
  406.         self.sourcedata = FeedParserDict()
  407.         self.contentparams = FeedParserDict()
  408.         self._summaryKey = None
  409.         self.namespacemap = { }
  410.         self.elementstack = []
  411.         self.basestack = []
  412.         self.langstack = []
  413.         if not baseuri:
  414.             pass
  415.         self.baseuri = ''
  416.         if not baselang:
  417.             pass
  418.         self.lang = None
  419.         if baselang:
  420.             self.feeddata['language'] = baselang
  421.         
  422.  
  423.     
  424.     def unknown_starttag(self, tag, attrs):
  425.         if _debug:
  426.             sys.stderr.write('start %s with %s\n' % (tag, attrs))
  427.         
  428.         attrs = [ (k.lower(), v) for k, v in attrs ]
  429.         attrs = [ (k, v) for k, v in attrs ]
  430.         attrsD = dict(attrs)
  431.         if not attrsD.get('xml:base', attrsD.get('base')):
  432.             pass
  433.         baseuri = self.baseuri
  434.         self.baseuri = _urljoin(self.baseuri, baseuri)
  435.         lang = attrsD.get('xml:lang', attrsD.get('lang'))
  436.         if lang == '':
  437.             lang = None
  438.         elif lang is None:
  439.             lang = self.lang
  440.         
  441.         self.lang = lang
  442.         self.basestack.append(self.baseuri)
  443.         self.langstack.append(lang)
  444.         for prefix, uri in attrs:
  445.             if prefix.startswith('xmlns:'):
  446.                 self.trackNamespace(prefix[6:], uri)
  447.                 continue
  448.             None if lang else []
  449.             if prefix == 'xmlns':
  450.                 self.trackNamespace(None, uri)
  451.                 continue
  452.         
  453.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  454.             self.contentparams['type'] = 'application/xhtml+xml'
  455.         
  456.         if tag.find(':') != -1:
  457.             (prefix, suffix) = tag.split(':', 1)
  458.         else:
  459.             prefix = ''
  460.             suffix = tag
  461.         prefix = self.namespacemap.get(prefix, prefix)
  462.         if prefix:
  463.             prefix = prefix + '_'
  464.         
  465.         if not prefix and tag not in ('title', 'link', 'description', 'name'):
  466.             self.intextinput = 0
  467.         
  468.         if not prefix and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
  469.             self.inimage = 0
  470.         
  471.         methodname = '_start_' + prefix + suffix
  472.         
  473.         try:
  474.             method = getattr(self, methodname)
  475.             return method(attrsD)
  476.         except AttributeError:
  477.             return self.push(prefix + suffix, 1)
  478.  
  479.  
  480.     
  481.     def unknown_endtag(self, tag):
  482.         if _debug:
  483.             sys.stderr.write('end %s\n' % tag)
  484.         
  485.         if tag.find(':') != -1:
  486.             (prefix, suffix) = tag.split(':', 1)
  487.         else:
  488.             prefix = ''
  489.             suffix = tag
  490.         prefix = self.namespacemap.get(prefix, prefix)
  491.         if prefix:
  492.             prefix = prefix + '_'
  493.         
  494.         methodname = '_end_' + prefix + suffix
  495.         
  496.         try:
  497.             method = getattr(self, methodname)
  498.             method()
  499.         except AttributeError:
  500.             self.pop(prefix + suffix)
  501.  
  502.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  503.             self.contentparams['type'] = 'application/xhtml+xml'
  504.         
  505.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  506.             tag = tag.split(':')[-1]
  507.             self.handle_data('</%s>' % tag, escape = 0)
  508.         
  509.         if self.basestack:
  510.             self.basestack.pop()
  511.             if self.basestack and self.basestack[-1]:
  512.                 self.baseuri = self.basestack[-1]
  513.             
  514.         
  515.         if self.langstack:
  516.             self.langstack.pop()
  517.             if self.langstack:
  518.                 self.lang = self.langstack[-1]
  519.             
  520.         
  521.  
  522.     
  523.     def handle_charref(self, ref):
  524.         if not self.elementstack:
  525.             return None
  526.         
  527.         ref = ref.lower()
  528.         if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
  529.             text = '&#%s;' % ref
  530.         elif ref[0] == 'x':
  531.             c = int(ref[1:], 16)
  532.         else:
  533.             c = int(ref)
  534.         text = unichr(c).encode('utf-8')
  535.         self.elementstack[-1][2].append(text)
  536.  
  537.     
  538.     def handle_entityref(self, ref):
  539.         if not self.elementstack:
  540.             return None
  541.         
  542.         if _debug:
  543.             sys.stderr.write('entering handle_entityref with %s\n' % ref)
  544.         
  545.         if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
  546.             text = '&%s;' % ref
  547.         else:
  548.             
  549.             def name2cp(k):
  550.                 import htmlentitydefs
  551.                 if hasattr(htmlentitydefs, 'name2codepoint'):
  552.                     return htmlentitydefs.name2codepoint[k]
  553.                 
  554.                 k = htmlentitydefs.entitydefs[k]
  555.                 if k.startswith('&#') and k.endswith(';'):
  556.                     return int(k[2:-1])
  557.                 
  558.                 return ord(k)
  559.  
  560.             
  561.             try:
  562.                 name2cp(ref)
  563.             except KeyError:
  564.                 text = '&%s;' % ref
  565.  
  566.             text = unichr(name2cp(ref)).encode('utf-8')
  567.         self.elementstack[-1][2].append(text)
  568.  
  569.     
  570.     def handle_data(self, text, escape = 1):
  571.         if not self.elementstack:
  572.             return None
  573.         
  574.         if escape and self.contentparams.get('type') == 'application/xhtml+xml':
  575.             text = _xmlescape(text)
  576.         
  577.         self.elementstack[-1][2].append(text)
  578.  
  579.     
  580.     def handle_comment(self, text):
  581.         pass
  582.  
  583.     
  584.     def handle_pi(self, text):
  585.         pass
  586.  
  587.     
  588.     def handle_decl(self, text):
  589.         pass
  590.  
  591.     
  592.     def parse_declaration(self, i):
  593.         if _debug:
  594.             sys.stderr.write('entering parse_declaration\n')
  595.         
  596.         if self.rawdata[i:i + 9] == '<![CDATA[':
  597.             k = self.rawdata.find(']]>', i)
  598.             if k == -1:
  599.                 k = len(self.rawdata)
  600.             
  601.             self.handle_data(_xmlescape(self.rawdata[i + 9:k]), 0)
  602.             return k + 3
  603.         else:
  604.             k = self.rawdata.find('>', i)
  605.             return k + 1
  606.  
  607.     
  608.     def mapContentType(self, contentType):
  609.         contentType = contentType.lower()
  610.         if contentType == 'text':
  611.             contentType = 'text/plain'
  612.         elif contentType == 'html':
  613.             contentType = 'text/html'
  614.         elif contentType == 'xhtml':
  615.             contentType = 'application/xhtml+xml'
  616.         
  617.         return contentType
  618.  
  619.     
  620.     def trackNamespace(self, prefix, uri):
  621.         loweruri = uri.lower()
  622.         if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not (self.version):
  623.             self.version = 'rss090'
  624.         
  625.         if loweruri == 'http://purl.org/rss/1.0/' and not (self.version):
  626.             self.version = 'rss10'
  627.         
  628.         if loweruri == 'http://www.w3.org/2005/atom' and not (self.version):
  629.             self.version = 'atom10'
  630.         
  631.         if loweruri.find('backend.userland.com/rss') != -1:
  632.             uri = 'http://backend.userland.com/rss'
  633.             loweruri = uri
  634.         
  635.         if self._matchnamespaces.has_key(loweruri):
  636.             self.namespacemap[prefix] = self._matchnamespaces[loweruri]
  637.             self.namespacesInUse[self._matchnamespaces[loweruri]] = uri
  638.         elif not prefix:
  639.             pass
  640.         self.namespacesInUse[''] = uri
  641.  
  642.     
  643.     def resolveURI(self, uri):
  644.         if not self.baseuri:
  645.             pass
  646.         return _urljoin('', uri)
  647.  
  648.     
  649.     def decodeEntities(self, element, data):
  650.         return data
  651.  
  652.     
  653.     def push(self, element, expectingText):
  654.         self.elementstack.append([
  655.             element,
  656.             expectingText,
  657.             []])
  658.  
  659.     
  660.     def pop(self, element, stripWhitespace = 1):
  661.         if not self.elementstack:
  662.             return None
  663.         
  664.         if self.elementstack[-1][0] != element:
  665.             return None
  666.         
  667.         (element, expectingText, pieces) = self.elementstack.pop()
  668.         output = ''.join(pieces)
  669.         if stripWhitespace:
  670.             output = output.strip()
  671.         
  672.         if not expectingText:
  673.             return output
  674.         
  675.         if base64 and self.contentparams.get('base64', 0):
  676.             
  677.             try:
  678.                 output = base64.decodestring(output)
  679.             except binascii.Error:
  680.                 pass
  681.             except binascii.Incomplete:
  682.                 pass
  683.             except:
  684.                 None<EXCEPTION MATCH>binascii.Error
  685.             
  686.  
  687.         None<EXCEPTION MATCH>binascii.Error
  688.         if element in self.can_be_relative_uri and output:
  689.             output = self.resolveURI(output)
  690.         
  691.         if not self.contentparams.get('base64', 0):
  692.             output = self.decodeEntities(element, output)
  693.         
  694.         
  695.         try:
  696.             del self.contentparams['mode']
  697.         except KeyError:
  698.             pass
  699.  
  700.         
  701.         try:
  702.             del self.contentparams['base64']
  703.         except KeyError:
  704.             pass
  705.  
  706.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  707.             if element in self.can_contain_relative_uris:
  708.                 output = _resolveRelativeURIs(output, self.baseuri, self.encoding)
  709.             
  710.         
  711.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  712.             if element in self.can_contain_dangerous_markup:
  713.                 output = _sanitizeHTML(output, self.encoding)
  714.             
  715.         
  716.         if self.encoding and type(output) != type(u''):
  717.             
  718.             try:
  719.                 output = unicode(output, self.encoding)
  720.  
  721.         
  722.         if element == 'category':
  723.             return output
  724.         
  725.         if self.inentry and not (self.insource):
  726.             if element == 'content':
  727.                 self.entries[-1].setdefault(element, [])
  728.                 contentparams = copy.deepcopy(self.contentparams)
  729.                 contentparams['value'] = output
  730.                 self.entries[-1][element].append(contentparams)
  731.             elif element == 'link':
  732.                 self.entries[-1][element] = output
  733.                 if output:
  734.                     self.entries[-1]['links'][-1]['href'] = output
  735.                 
  736.             elif element == 'description':
  737.                 element = 'summary'
  738.             
  739.             self.entries[-1][element] = output
  740.             if self.incontent:
  741.                 contentparams = copy.deepcopy(self.contentparams)
  742.                 contentparams['value'] = output
  743.                 self.entries[-1][element + '_detail'] = contentparams
  744.             
  745.         elif (self.infeed or self.insource) and not (self.intextinput) and not (self.inimage):
  746.             context = self._getContext()
  747.             if element == 'description':
  748.                 element = 'subtitle'
  749.             
  750.             context[element] = output
  751.             if element == 'link':
  752.                 context['links'][-1]['href'] = output
  753.             elif self.incontent:
  754.                 contentparams = copy.deepcopy(self.contentparams)
  755.                 contentparams['value'] = output
  756.                 context[element + '_detail'] = contentparams
  757.             
  758.         
  759.         return output
  760.  
  761.     
  762.     def pushContent(self, tag, attrsD, defaultContentType, expectingText):
  763.         self.incontent += 1
  764.         self.contentparams = FeedParserDict({
  765.             'type': self.mapContentType(attrsD.get('type', defaultContentType)),
  766.             'language': self.lang,
  767.             'base': self.baseuri })
  768.         self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
  769.         self.push(tag, expectingText)
  770.  
  771.     
  772.     def popContent(self, tag):
  773.         value = self.pop(tag)
  774.         self.incontent -= 1
  775.         self.contentparams.clear()
  776.         return value
  777.  
  778.     
  779.     def _mapToStandardPrefix(self, name):
  780.         colonpos = name.find(':')
  781.         if colonpos != -1:
  782.             prefix = name[:colonpos]
  783.             suffix = name[colonpos + 1:]
  784.             prefix = self.namespacemap.get(prefix, prefix)
  785.             name = prefix + ':' + suffix
  786.         
  787.         return name
  788.  
  789.     
  790.     def _getAttribute(self, attrsD, name):
  791.         return attrsD.get(self._mapToStandardPrefix(name))
  792.  
  793.     
  794.     def _isBase64(self, attrsD, contentparams):
  795.         if attrsD.get('mode', '') == 'base64':
  796.             return 1
  797.         
  798.         if self.contentparams['type'].startswith('text/'):
  799.             return 0
  800.         
  801.         if self.contentparams['type'].endswith('+xml'):
  802.             return 0
  803.         
  804.         if self.contentparams['type'].endswith('/xml'):
  805.             return 0
  806.         
  807.         return 1
  808.  
  809.     
  810.     def _itsAnHrefDamnIt(self, attrsD):
  811.         href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
  812.         if href:
  813.             
  814.             try:
  815.                 del attrsD['url']
  816.             except KeyError:
  817.                 pass
  818.  
  819.             
  820.             try:
  821.                 del attrsD['uri']
  822.             except KeyError:
  823.                 pass
  824.  
  825.             attrsD['href'] = href
  826.         
  827.         return attrsD
  828.  
  829.     
  830.     def _save(self, key, value):
  831.         context = self._getContext()
  832.         context.setdefault(key, value)
  833.  
  834.     
  835.     def _start_rss(self, attrsD):
  836.         versionmap = {
  837.             '0.91': 'rss091u',
  838.             '0.92': 'rss092',
  839.             '0.93': 'rss093',
  840.             '0.94': 'rss094' }
  841.         if not self.version:
  842.             attr_version = attrsD.get('version', '')
  843.             version = versionmap.get(attr_version)
  844.             if version:
  845.                 self.version = version
  846.             elif attr_version.startswith('2.'):
  847.                 self.version = 'rss20'
  848.             else:
  849.                 self.version = 'rss'
  850.         
  851.  
  852.     
  853.     def _start_dlhottitles(self, attrsD):
  854.         self.version = 'hotrss'
  855.  
  856.     
  857.     def _start_channel(self, attrsD):
  858.         self.infeed = 1
  859.         self._cdf_common(attrsD)
  860.  
  861.     _start_feedinfo = _start_channel
  862.     
  863.     def _cdf_common(self, attrsD):
  864.         if attrsD.has_key('lastmod'):
  865.             self._start_modified({ })
  866.             self.elementstack[-1][-1] = attrsD['lastmod']
  867.             self._end_modified()
  868.         
  869.         if attrsD.has_key('href'):
  870.             self._start_link({ })
  871.             self.elementstack[-1][-1] = attrsD['href']
  872.             self._end_link()
  873.         
  874.  
  875.     
  876.     def _start_feed(self, attrsD):
  877.         self.infeed = 1
  878.         versionmap = {
  879.             '0.1': 'atom01',
  880.             '0.2': 'atom02',
  881.             '0.3': 'atom03' }
  882.         if not self.version:
  883.             attr_version = attrsD.get('version')
  884.             version = versionmap.get(attr_version)
  885.             if version:
  886.                 self.version = version
  887.             else:
  888.                 self.version = 'atom'
  889.         
  890.  
  891.     
  892.     def _end_channel(self):
  893.         self.infeed = 0
  894.  
  895.     _end_feed = _end_channel
  896.     
  897.     def _start_image(self, attrsD):
  898.         self.inimage = 1
  899.         self.push('image', 0)
  900.         context = self._getContext()
  901.         context.setdefault('image', FeedParserDict())
  902.  
  903.     
  904.     def _end_image(self):
  905.         self.pop('image')
  906.         self.inimage = 0
  907.  
  908.     
  909.     def _start_textinput(self, attrsD):
  910.         self.intextinput = 1
  911.         self.push('textinput', 0)
  912.         context = self._getContext()
  913.         context.setdefault('textinput', FeedParserDict())
  914.  
  915.     _start_textInput = _start_textinput
  916.     
  917.     def _end_textinput(self):
  918.         self.pop('textinput')
  919.         self.intextinput = 0
  920.  
  921.     _end_textInput = _end_textinput
  922.     
  923.     def _start_author(self, attrsD):
  924.         self.inauthor = 1
  925.         self.push('author', 1)
  926.  
  927.     _start_managingeditor = _start_author
  928.     _start_dc_author = _start_author
  929.     _start_dc_creator = _start_author
  930.     _start_itunes_author = _start_author
  931.     
  932.     def _end_author(self):
  933.         self.pop('author')
  934.         self.inauthor = 0
  935.         self._sync_author_detail()
  936.  
  937.     _end_managingeditor = _end_author
  938.     _end_dc_author = _end_author
  939.     _end_dc_creator = _end_author
  940.     _end_itunes_author = _end_author
  941.     
  942.     def _start_itunes_owner(self, attrsD):
  943.         self.inpublisher = 1
  944.         self.push('publisher', 0)
  945.  
  946.     
  947.     def _end_itunes_owner(self):
  948.         self.pop('publisher')
  949.         self.inpublisher = 0
  950.         self._sync_author_detail('publisher')
  951.  
  952.     
  953.     def _start_contributor(self, attrsD):
  954.         self.incontributor = 1
  955.         context = self._getContext()
  956.         context.setdefault('contributors', [])
  957.         context['contributors'].append(FeedParserDict())
  958.         self.push('contributor', 0)
  959.  
  960.     
  961.     def _end_contributor(self):
  962.         self.pop('contributor')
  963.         self.incontributor = 0
  964.  
  965.     
  966.     def _start_dc_contributor(self, attrsD):
  967.         self.incontributor = 1
  968.         context = self._getContext()
  969.         context.setdefault('contributors', [])
  970.         context['contributors'].append(FeedParserDict())
  971.         self.push('name', 0)
  972.  
  973.     
  974.     def _end_dc_contributor(self):
  975.         self._end_name()
  976.         self.incontributor = 0
  977.  
  978.     
  979.     def _start_name(self, attrsD):
  980.         self.push('name', 0)
  981.  
  982.     _start_itunes_name = _start_name
  983.     
  984.     def _end_name(self):
  985.         value = self.pop('name')
  986.         if self.inpublisher:
  987.             self._save_author('name', value, 'publisher')
  988.         elif self.inauthor:
  989.             self._save_author('name', value)
  990.         elif self.incontributor:
  991.             self._save_contributor('name', value)
  992.         elif self.intextinput:
  993.             context = self._getContext()
  994.             context['textinput']['name'] = value
  995.         
  996.  
  997.     _end_itunes_name = _end_name
  998.     
  999.     def _start_width(self, attrsD):
  1000.         self.push('width', 0)
  1001.  
  1002.     
  1003.     def _end_width(self):
  1004.         value = self.pop('width')
  1005.         
  1006.         try:
  1007.             value = int(value)
  1008.         except:
  1009.             value = 0
  1010.  
  1011.         if self.inimage:
  1012.             context = self._getContext()
  1013.             context['image']['width'] = value
  1014.         
  1015.  
  1016.     
  1017.     def _start_height(self, attrsD):
  1018.         self.push('height', 0)
  1019.  
  1020.     
  1021.     def _end_height(self):
  1022.         value = self.pop('height')
  1023.         
  1024.         try:
  1025.             value = int(value)
  1026.         except:
  1027.             value = 0
  1028.  
  1029.         if self.inimage:
  1030.             context = self._getContext()
  1031.             context['image']['height'] = value
  1032.         
  1033.  
  1034.     
  1035.     def _start_url(self, attrsD):
  1036.         self.push('href', 1)
  1037.  
  1038.     _start_homepage = _start_url
  1039.     _start_uri = _start_url
  1040.     
  1041.     def _end_url(self):
  1042.         value = self.pop('href')
  1043.         if self.inauthor:
  1044.             self._save_author('href', value)
  1045.         elif self.incontributor:
  1046.             self._save_contributor('href', value)
  1047.         elif self.inimage:
  1048.             context = self._getContext()
  1049.             context['image']['href'] = value
  1050.         elif self.intextinput:
  1051.             context = self._getContext()
  1052.             context['textinput']['link'] = value
  1053.         
  1054.  
  1055.     _end_homepage = _end_url
  1056.     _end_uri = _end_url
  1057.     
  1058.     def _start_email(self, attrsD):
  1059.         self.push('email', 0)
  1060.  
  1061.     _start_itunes_email = _start_email
  1062.     
  1063.     def _end_email(self):
  1064.         value = self.pop('email')
  1065.         if self.inpublisher:
  1066.             self._save_author('email', value, 'publisher')
  1067.         elif self.inauthor:
  1068.             self._save_author('email', value)
  1069.         elif self.incontributor:
  1070.             self._save_contributor('email', value)
  1071.         
  1072.  
  1073.     _end_itunes_email = _end_email
  1074.     
  1075.     def _getContext(self):
  1076.         if self.insource:
  1077.             context = self.sourcedata
  1078.         elif self.inentry:
  1079.             context = self.entries[-1]
  1080.         else:
  1081.             context = self.feeddata
  1082.         return context
  1083.  
  1084.     
  1085.     def _save_author(self, key, value, prefix = 'author'):
  1086.         context = self._getContext()
  1087.         context.setdefault(prefix + '_detail', FeedParserDict())
  1088.         context[prefix + '_detail'][key] = value
  1089.         self._sync_author_detail()
  1090.  
  1091.     
  1092.     def _save_contributor(self, key, value):
  1093.         context = self._getContext()
  1094.         context.setdefault('contributors', [
  1095.             FeedParserDict()])
  1096.         context['contributors'][-1][key] = value
  1097.  
  1098.     
  1099.     def _sync_author_detail(self, key = 'author'):
  1100.         context = self._getContext()
  1101.         detail = context.get('%s_detail' % key)
  1102.         if detail:
  1103.             name = detail.get('name')
  1104.             email = detail.get('email')
  1105.             if name and email:
  1106.                 context[key] = '%s (%s)' % (name, email)
  1107.             elif name:
  1108.                 context[key] = name
  1109.             elif email:
  1110.                 context[key] = email
  1111.             
  1112.         else:
  1113.             author = context.get(key)
  1114.             if not author:
  1115.                 return None
  1116.             
  1117.             emailmatch = re.search('(([a-zA-Z0-9\\_\\-\\.\\+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?))', author)
  1118.             if not emailmatch:
  1119.                 return None
  1120.             
  1121.             email = emailmatch.group(0)
  1122.             author = author.replace(email, '')
  1123.             author = author.replace('()', '')
  1124.             author = author.strip()
  1125.             if author and author[0] == '(':
  1126.                 author = author[1:]
  1127.             
  1128.             if author and author[-1] == ')':
  1129.                 author = author[:-1]
  1130.             
  1131.             author = author.strip()
  1132.             context.setdefault('%s_detail' % key, FeedParserDict())
  1133.             context['%s_detail' % key]['name'] = author
  1134.             context['%s_detail' % key]['email'] = email
  1135.  
  1136.     
  1137.     def _start_subtitle(self, attrsD):
  1138.         self.pushContent('subtitle', attrsD, 'text/plain', 1)
  1139.  
  1140.     _start_tagline = _start_subtitle
  1141.     _start_itunes_subtitle = _start_subtitle
  1142.     
  1143.     def _end_subtitle(self):
  1144.         self.popContent('subtitle')
  1145.  
  1146.     _end_tagline = _end_subtitle
  1147.     _end_itunes_subtitle = _end_subtitle
  1148.     
  1149.     def _start_rights(self, attrsD):
  1150.         self.pushContent('rights', attrsD, 'text/plain', 1)
  1151.  
  1152.     _start_dc_rights = _start_rights
  1153.     _start_copyright = _start_rights
  1154.     
  1155.     def _end_rights(self):
  1156.         self.popContent('rights')
  1157.  
  1158.     _end_dc_rights = _end_rights
  1159.     _end_copyright = _end_rights
  1160.     
  1161.     def _start_item(self, attrsD):
  1162.         self.entries.append(FeedParserDict())
  1163.         self.push('item', 0)
  1164.         self.inentry = 1
  1165.         self.guidislink = 0
  1166.         id = self._getAttribute(attrsD, 'rdf:about')
  1167.         if id:
  1168.             context = self._getContext()
  1169.             context['id'] = id
  1170.         
  1171.         self._cdf_common(attrsD)
  1172.  
  1173.     _start_entry = _start_item
  1174.     _start_product = _start_item
  1175.     
  1176.     def _end_item(self):
  1177.         self.pop('item')
  1178.         self.inentry = 0
  1179.  
  1180.     _end_entry = _end_item
  1181.     
  1182.     def _start_dc_language(self, attrsD):
  1183.         self.push('language', 1)
  1184.  
  1185.     _start_language = _start_dc_language
  1186.     
  1187.     def _end_dc_language(self):
  1188.         self.lang = self.pop('language')
  1189.  
  1190.     _end_language = _end_dc_language
  1191.     
  1192.     def _start_dc_publisher(self, attrsD):
  1193.         self.push('publisher', 1)
  1194.  
  1195.     _start_webmaster = _start_dc_publisher
  1196.     
  1197.     def _end_dc_publisher(self):
  1198.         self.pop('publisher')
  1199.         self._sync_author_detail('publisher')
  1200.  
  1201.     _end_webmaster = _end_dc_publisher
  1202.     
  1203.     def _start_published(self, attrsD):
  1204.         self.push('published', 1)
  1205.  
  1206.     _start_dcterms_issued = _start_published
  1207.     _start_issued = _start_published
  1208.     
  1209.     def _end_published(self):
  1210.         value = self.pop('published')
  1211.         self._save('published_parsed', _parse_date(value))
  1212.  
  1213.     _end_dcterms_issued = _end_published
  1214.     _end_issued = _end_published
  1215.     
  1216.     def _start_updated(self, attrsD):
  1217.         self.push('updated', 1)
  1218.  
  1219.     _start_modified = _start_updated
  1220.     _start_dcterms_modified = _start_updated
  1221.     _start_pubdate = _start_updated
  1222.     _start_dc_date = _start_updated
  1223.     
  1224.     def _end_updated(self):
  1225.         value = self.pop('updated')
  1226.         parsed_value = _parse_date(value)
  1227.         self._save('updated_parsed', parsed_value)
  1228.  
  1229.     _end_modified = _end_updated
  1230.     _end_dcterms_modified = _end_updated
  1231.     _end_pubdate = _end_updated
  1232.     _end_dc_date = _end_updated
  1233.     
  1234.     def _start_created(self, attrsD):
  1235.         self.push('created', 1)
  1236.  
  1237.     _start_dcterms_created = _start_created
  1238.     
  1239.     def _end_created(self):
  1240.         value = self.pop('created')
  1241.         self._save('created_parsed', _parse_date(value))
  1242.  
  1243.     _end_dcterms_created = _end_created
  1244.     
  1245.     def _start_expirationdate(self, attrsD):
  1246.         self.push('expired', 1)
  1247.  
  1248.     
  1249.     def _end_expirationdate(self):
  1250.         self._save('expired_parsed', _parse_date(self.pop('expired')))
  1251.  
  1252.     
  1253.     def _start_cc_license(self, attrsD):
  1254.         self.push('license', 1)
  1255.         value = self._getAttribute(attrsD, 'rdf:resource')
  1256.         if value:
  1257.             self.elementstack[-1][2].append(value)
  1258.         
  1259.         self.pop('license')
  1260.  
  1261.     
  1262.     def _start_creativecommons_license(self, attrsD):
  1263.         self.push('license', 1)
  1264.  
  1265.     
  1266.     def _end_creativecommons_license(self):
  1267.         self.pop('license')
  1268.  
  1269.     
  1270.     def _addTag(self, term, scheme, label):
  1271.         context = self._getContext()
  1272.         tags = context.setdefault('tags', [])
  1273.         if not term and not scheme and not label:
  1274.             return None
  1275.         
  1276.         value = FeedParserDict({
  1277.             'term': term,
  1278.             'scheme': scheme,
  1279.             'label': label })
  1280.         if value not in tags:
  1281.             tags.append(FeedParserDict({
  1282.                 'term': term,
  1283.                 'scheme': scheme,
  1284.                 'label': label }))
  1285.         
  1286.  
  1287.     
  1288.     def _start_category(self, attrsD):
  1289.         if _debug:
  1290.             sys.stderr.write('entering _start_category with %s\n' % repr(attrsD))
  1291.         
  1292.         term = attrsD.get('term')
  1293.         scheme = attrsD.get('scheme', attrsD.get('domain'))
  1294.         label = attrsD.get('label')
  1295.         self._addTag(term, scheme, label)
  1296.         self.push('category', 1)
  1297.  
  1298.     _start_dc_subject = _start_category
  1299.     _start_keywords = _start_category
  1300.     
  1301.     def _end_itunes_keywords(self):
  1302.         for term in self.pop('itunes_keywords').split():
  1303.             self._addTag(term, 'http://www.itunes.com/', None)
  1304.         
  1305.  
  1306.     
  1307.     def _start_itunes_category(self, attrsD):
  1308.         self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
  1309.         self.push('category', 1)
  1310.  
  1311.     
  1312.     def _end_category(self):
  1313.         value = self.pop('category')
  1314.         if not value:
  1315.             return None
  1316.         
  1317.         context = self._getContext()
  1318.         tags = context['tags']
  1319.         if value and len(tags) and not tags[-1]['term']:
  1320.             tags[-1]['term'] = value
  1321.         else:
  1322.             self._addTag(value, None, None)
  1323.  
  1324.     _end_dc_subject = _end_category
  1325.     _end_keywords = _end_category
  1326.     _end_itunes_category = _end_category
  1327.     
  1328.     def _start_cloud(self, attrsD):
  1329.         self._getContext()['cloud'] = FeedParserDict(attrsD)
  1330.  
  1331.     
  1332.     def _start_link(self, attrsD):
  1333.         attrsD.setdefault('rel', 'alternate')
  1334.         attrsD.setdefault('type', 'text/html')
  1335.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1336.         if attrsD.has_key('href'):
  1337.             attrsD['href'] = self.resolveURI(attrsD['href'])
  1338.         
  1339.         if not self.infeed and self.inentry:
  1340.             pass
  1341.         expectingText = self.insource
  1342.         context = self._getContext()
  1343.         context.setdefault('links', [])
  1344.         context['links'].append(FeedParserDict(attrsD))
  1345.         if attrsD['rel'] == 'enclosure':
  1346.             self._start_enclosure(attrsD)
  1347.         
  1348.         if attrsD.has_key('href'):
  1349.             expectingText = 0
  1350.             if attrsD.get('rel') == 'alternate' and self.mapContentType(attrsD.get('type')) in self.html_types:
  1351.                 context['link'] = attrsD['href']
  1352.             
  1353.         else:
  1354.             self.push('link', expectingText)
  1355.  
  1356.     _start_producturl = _start_link
  1357.     
  1358.     def _end_link(self):
  1359.         value = self.pop('link')
  1360.         context = self._getContext()
  1361.         if self.intextinput:
  1362.             context['textinput']['link'] = value
  1363.         
  1364.         if self.inimage:
  1365.             context['image']['link'] = value
  1366.         
  1367.  
  1368.     _end_producturl = _end_link
  1369.     
  1370.     def _start_guid(self, attrsD):
  1371.         self.guidislink = attrsD.get('ispermalink', 'true') == 'true'
  1372.         self.push('id', 1)
  1373.  
  1374.     
  1375.     def _end_guid(self):
  1376.         value = self.pop('id')
  1377.         if self.guidislink:
  1378.             pass
  1379.         self._save('guidislink', not self._getContext().has_key('link'))
  1380.         if self.guidislink:
  1381.             self._save('link', value)
  1382.         
  1383.  
  1384.     
  1385.     def _start_title(self, attrsD):
  1386.         if not self.infeed and self.inentry:
  1387.             pass
  1388.         self.pushContent('title', attrsD, 'text/plain', self.insource)
  1389.  
  1390.     _start_dc_title = _start_title
  1391.     _start_media_title = _start_title
  1392.     
  1393.     def _end_title(self):
  1394.         value = self.popContent('title')
  1395.         context = self._getContext()
  1396.         if self.intextinput:
  1397.             context['textinput']['title'] = value
  1398.         elif self.inimage:
  1399.             context['image']['title'] = value
  1400.         
  1401.  
  1402.     _end_dc_title = _end_title
  1403.     _end_media_title = _end_title
  1404.     
  1405.     def _start_description(self, attrsD):
  1406.         context = self._getContext()
  1407.         if context.has_key('summary'):
  1408.             self._summaryKey = 'content'
  1409.             self._start_content(attrsD)
  1410.         elif not self.infeed and self.inentry:
  1411.             pass
  1412.         self.pushContent('description', attrsD, 'text/html', self.insource)
  1413.  
  1414.     
  1415.     def _start_abstract(self, attrsD):
  1416.         if not self.infeed and self.inentry:
  1417.             pass
  1418.         self.pushContent('description', attrsD, 'text/plain', self.insource)
  1419.  
  1420.     
  1421.     def _end_description(self):
  1422.         if self._summaryKey == 'content':
  1423.             self._end_content()
  1424.         else:
  1425.             value = self.popContent('description')
  1426.             context = self._getContext()
  1427.             if self.intextinput:
  1428.                 context['textinput']['description'] = value
  1429.             elif self.inimage:
  1430.                 context['image']['description'] = value
  1431.             
  1432.         self._summaryKey = None
  1433.  
  1434.     _end_abstract = _end_description
  1435.     
  1436.     def _start_info(self, attrsD):
  1437.         self.pushContent('info', attrsD, 'text/plain', 1)
  1438.  
  1439.     _start_feedburner_browserfriendly = _start_info
  1440.     
  1441.     def _end_info(self):
  1442.         self.popContent('info')
  1443.  
  1444.     _end_feedburner_browserfriendly = _end_info
  1445.     
  1446.     def _start_generator(self, attrsD):
  1447.         if attrsD:
  1448.             attrsD = self._itsAnHrefDamnIt(attrsD)
  1449.             if attrsD.has_key('href'):
  1450.                 attrsD['href'] = self.resolveURI(attrsD['href'])
  1451.             
  1452.         
  1453.         self._getContext()['generator_detail'] = FeedParserDict(attrsD)
  1454.         self.push('generator', 1)
  1455.  
  1456.     
  1457.     def _end_generator(self):
  1458.         value = self.pop('generator')
  1459.         context = self._getContext()
  1460.         if context.has_key('generator_detail'):
  1461.             context['generator_detail']['name'] = value
  1462.         
  1463.  
  1464.     
  1465.     def _start_admin_generatoragent(self, attrsD):
  1466.         self.push('generator', 1)
  1467.         value = self._getAttribute(attrsD, 'rdf:resource')
  1468.         if value:
  1469.             self.elementstack[-1][2].append(value)
  1470.         
  1471.         self.pop('generator')
  1472.         self._getContext()['generator_detail'] = FeedParserDict({
  1473.             'href': value })
  1474.  
  1475.     
  1476.     def _start_admin_errorreportsto(self, attrsD):
  1477.         self.push('errorreportsto', 1)
  1478.         value = self._getAttribute(attrsD, 'rdf:resource')
  1479.         if value:
  1480.             self.elementstack[-1][2].append(value)
  1481.         
  1482.         self.pop('errorreportsto')
  1483.  
  1484.     
  1485.     def _start_summary(self, attrsD):
  1486.         context = self._getContext()
  1487.         if context.has_key('summary'):
  1488.             self._summaryKey = 'content'
  1489.             self._start_content(attrsD)
  1490.         else:
  1491.             self._summaryKey = 'summary'
  1492.             self.pushContent(self._summaryKey, attrsD, 'text/plain', 1)
  1493.  
  1494.     _start_itunes_summary = _start_summary
  1495.     
  1496.     def _end_summary(self):
  1497.         if self._summaryKey == 'content':
  1498.             self._end_content()
  1499.         elif not self._summaryKey:
  1500.             pass
  1501.         self.popContent('summary')
  1502.         self._summaryKey = None
  1503.  
  1504.     _end_itunes_summary = _end_summary
  1505.     
  1506.     def _start_enclosure(self, attrsD):
  1507.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1508.         self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD))
  1509.         href = attrsD.get('href')
  1510.         if href:
  1511.             context = self._getContext()
  1512.             if not context.get('id'):
  1513.                 context['id'] = href
  1514.             
  1515.         
  1516.  
  1517.     
  1518.     def _start_source(self, attrsD):
  1519.         self.insource = 1
  1520.  
  1521.     
  1522.     def _end_source(self):
  1523.         self.insource = 0
  1524.         self._getContext()['source'] = copy.deepcopy(self.sourcedata)
  1525.         self.sourcedata.clear()
  1526.  
  1527.     
  1528.     def _start_content(self, attrsD):
  1529.         self.pushContent('content', attrsD, 'text/plain', 1)
  1530.         src = attrsD.get('src')
  1531.         if src:
  1532.             self.contentparams['src'] = src
  1533.         
  1534.         self.push('content', 1)
  1535.  
  1536.     
  1537.     def _start_prodlink(self, attrsD):
  1538.         self.pushContent('content', attrsD, 'text/html', 1)
  1539.  
  1540.     
  1541.     def _start_body(self, attrsD):
  1542.         self.pushContent('content', attrsD, 'application/xhtml+xml', 1)
  1543.  
  1544.     _start_xhtml_body = _start_body
  1545.     
  1546.     def _start_content_encoded(self, attrsD):
  1547.         self.pushContent('content', attrsD, 'text/html', 1)
  1548.  
  1549.     _start_fullitem = _start_content_encoded
  1550.     
  1551.     def _end_content(self):
  1552.         copyToDescription = self.mapContentType(self.contentparams.get('type')) in [
  1553.             'text/plain'] + self.html_types
  1554.         value = self.popContent('content')
  1555.         if copyToDescription:
  1556.             self._save('description', value)
  1557.         
  1558.  
  1559.     _end_body = _end_content
  1560.     _end_xhtml_body = _end_content
  1561.     _end_content_encoded = _end_content
  1562.     _end_fullitem = _end_content
  1563.     _end_prodlink = _end_content
  1564.     
  1565.     def _start_itunes_image(self, attrsD):
  1566.         self.push('itunes_image', 0)
  1567.         self._getContext()['image'] = FeedParserDict({
  1568.             'href': attrsD.get('href') })
  1569.  
  1570.     _start_itunes_link = _start_itunes_image
  1571.     
  1572.     def _end_itunes_block(self):
  1573.         value = self.pop('itunes_block', 0)
  1574.         if not value == 'yes' or 1:
  1575.             pass
  1576.         self._getContext()['itunes_block'] = 0
  1577.  
  1578.     
  1579.     def _end_itunes_explicit(self):
  1580.         value = self.pop('itunes_explicit', 0)
  1581.         if not value == 'yes' or 1:
  1582.             pass
  1583.         self._getContext()['itunes_explicit'] = 0
  1584.  
  1585.  
  1586. if _XML_AVAILABLE:
  1587.     
  1588.     class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
  1589.         
  1590.         def __init__(self, baseuri, baselang, encoding):
  1591.             if _debug:
  1592.                 sys.stderr.write('trying StrictFeedParser\n')
  1593.             
  1594.             xml.sax.handler.ContentHandler.__init__(self)
  1595.             _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1596.             self.bozo = 0
  1597.             self.exc = None
  1598.  
  1599.         
  1600.         def startPrefixMapping(self, prefix, uri):
  1601.             self.trackNamespace(prefix, uri)
  1602.  
  1603.         
  1604.         def startElementNS(self, name, qname, attrs):
  1605.             (namespace, localname) = name
  1606.             if not namespace:
  1607.                 pass
  1608.             lowernamespace = str('').lower()
  1609.             if lowernamespace.find('backend.userland.com/rss') != -1:
  1610.                 namespace = 'http://backend.userland.com/rss'
  1611.                 lowernamespace = namespace
  1612.             
  1613.             if qname and qname.find(':') > 0:
  1614.                 givenprefix = qname.split(':')[0]
  1615.             else:
  1616.                 givenprefix = None
  1617.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1618.             if givenprefix:
  1619.                 if (prefix == None or prefix == '' or lowernamespace == '') and not self.namespacesInUse.has_key(givenprefix):
  1620.                     raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
  1621.                 
  1622.             if prefix:
  1623.                 localname = prefix + ':' + localname
  1624.             
  1625.             localname = str(localname).lower()
  1626.             if _debug:
  1627.                 sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname))
  1628.             
  1629.             attrsD = { }
  1630.             for namespace, attrlocalname in attrs._attrs.items():
  1631.                 attrvalue = None
  1632.                 if not namespace:
  1633.                     pass
  1634.                 lowernamespace = ''.lower()
  1635.                 prefix = self._matchnamespaces.get(lowernamespace, '')
  1636.                 if prefix:
  1637.                     attrlocalname = prefix + ':' + attrlocalname
  1638.                 
  1639.                 attrsD[str(attrlocalname).lower()] = attrvalue
  1640.             
  1641.             for qname in attrs.getQNames():
  1642.                 attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
  1643.             
  1644.             self.unknown_starttag(localname, attrsD.items())
  1645.  
  1646.         
  1647.         def characters(self, text):
  1648.             self.handle_data(text)
  1649.  
  1650.         
  1651.         def endElementNS(self, name, qname):
  1652.             (namespace, localname) = name
  1653.             if not namespace:
  1654.                 pass
  1655.             lowernamespace = str('').lower()
  1656.             if qname and qname.find(':') > 0:
  1657.                 givenprefix = qname.split(':')[0]
  1658.             else:
  1659.                 givenprefix = ''
  1660.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1661.             if prefix:
  1662.                 localname = prefix + ':' + localname
  1663.             
  1664.             localname = str(localname).lower()
  1665.             self.unknown_endtag(localname)
  1666.  
  1667.         
  1668.         def error(self, exc):
  1669.             self.bozo = 1
  1670.             self.exc = exc
  1671.  
  1672.         
  1673.         def fatalError(self, exc):
  1674.             self.error(exc)
  1675.             raise exc
  1676.  
  1677.  
  1678.  
  1679.  
  1680. class _BaseHTMLProcessor(sgmllib.SGMLParser):
  1681.     elements_no_end_tag = [
  1682.         'area',
  1683.         'base',
  1684.         'basefont',
  1685.         'br',
  1686.         'col',
  1687.         'frame',
  1688.         'hr',
  1689.         'img',
  1690.         'input',
  1691.         'isindex',
  1692.         'link',
  1693.         'meta',
  1694.         'param']
  1695.     
  1696.     def __init__(self, encoding):
  1697.         self.encoding = encoding
  1698.         if _debug:
  1699.             sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
  1700.         
  1701.         sgmllib.SGMLParser.__init__(self)
  1702.  
  1703.     
  1704.     def reset(self):
  1705.         self.pieces = []
  1706.         sgmllib.SGMLParser.reset(self)
  1707.  
  1708.     
  1709.     def _shorttag_replace(self, match):
  1710.         tag = match.group(1)
  1711.         if tag in self.elements_no_end_tag:
  1712.             return '<' + tag + ' />'
  1713.         else:
  1714.             return '<' + tag + '></' + tag + '>'
  1715.  
  1716.     
  1717.     def feed(self, data):
  1718.         data = re.compile('<!((?!DOCTYPE|--|\\[))', re.IGNORECASE).sub('<!\\1', data)
  1719.         data = re.sub('<([^<\\s]+?)\\s*/>', self._shorttag_replace, data)
  1720.         data = data.replace(''', "'")
  1721.         data = data.replace('"', '"')
  1722.         if self.encoding and type(data) == type(u''):
  1723.             data = data.encode(self.encoding)
  1724.         
  1725.         sgmllib.SGMLParser.feed(self, data)
  1726.  
  1727.     
  1728.     def normalize_attrs(self, attrs):
  1729.         attrs = [ (k.lower(), v) for k, v in attrs ]
  1730.         attrs = [ (k, v) for k, v in attrs ]
  1731.         return attrs
  1732.  
  1733.     
  1734.     def unknown_starttag(self, tag, attrs):
  1735.         if _debug:
  1736.             sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
  1737.         
  1738.         uattrs = []
  1739.         for key, value in attrs:
  1740.             if type(value) != type(u''):
  1741.                 value = unicode(value, self.encoding)
  1742.             
  1743.             uattrs.append((unicode(key, self.encoding), value))
  1744.         
  1745.         strattrs = []([ u' %s="%s"' % (key, value) for key, value in uattrs ]).encode(self.encoding)
  1746.  
  1747.     
  1748.     def unknown_endtag(self, tag):
  1749.         if tag not in self.elements_no_end_tag:
  1750.             self.pieces.append('</%(tag)s>' % locals())
  1751.         
  1752.  
  1753.     
  1754.     def handle_charref(self, ref):
  1755.         self.pieces.append('&#%(ref)s;' % locals())
  1756.  
  1757.     
  1758.     def handle_entityref(self, ref):
  1759.         self.pieces.append('&%(ref)s;' % locals())
  1760.  
  1761.     
  1762.     def handle_data(self, text):
  1763.         if _debug:
  1764.             sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text)
  1765.         
  1766.         self.pieces.append(text)
  1767.  
  1768.     
  1769.     def handle_comment(self, text):
  1770.         self.pieces.append('<!--%(text)s-->' % locals())
  1771.  
  1772.     
  1773.     def handle_pi(self, text):
  1774.         self.pieces.append('<?%(text)s>' % locals())
  1775.  
  1776.     
  1777.     def handle_decl(self, text):
  1778.         self.pieces.append('<!%(text)s>' % locals())
  1779.  
  1780.     _new_declname_match = re.compile('[a-zA-Z][-_.a-zA-Z0-9:]*\\s*').match
  1781.     
  1782.     def _scan_name(self, i, declstartpos):
  1783.         rawdata = self.rawdata
  1784.         n = len(rawdata)
  1785.         if i == n:
  1786.             return (None, -1)
  1787.         
  1788.         m = self._new_declname_match(rawdata, i)
  1789.         if m:
  1790.             s = m.group()
  1791.             name = s.strip()
  1792.             if i + len(s) == n:
  1793.                 return (None, -1)
  1794.             
  1795.             return (name.lower(), m.end())
  1796.         else:
  1797.             self.handle_data(rawdata)
  1798.             return (None, -1)
  1799.  
  1800.     
  1801.     def output(self):
  1802.         return []([ str(p) for p in self.pieces ])
  1803.  
  1804.  
  1805.  
  1806. class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
  1807.     
  1808.     def __init__(self, baseuri, baselang, encoding):
  1809.         sgmllib.SGMLParser.__init__(self)
  1810.         _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1811.  
  1812.     
  1813.     def decodeEntities(self, element, data):
  1814.         data = data.replace('<', '<')
  1815.         data = data.replace('<', '<')
  1816.         data = data.replace('>', '>')
  1817.         data = data.replace('>', '>')
  1818.         data = data.replace('&', '&')
  1819.         data = data.replace('&', '&')
  1820.         data = data.replace('"', '"')
  1821.         data = data.replace('"', '"')
  1822.         data = data.replace(''', ''')
  1823.         data = data.replace(''', ''')
  1824.         if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  1825.             data = data.replace('<', '<')
  1826.             data = data.replace('>', '>')
  1827.             data = data.replace('&', '&')
  1828.             data = data.replace('"', '"')
  1829.             data = data.replace(''', "'")
  1830.         
  1831.         return data
  1832.  
  1833.  
  1834.  
  1835. class _RelativeURIResolver(_BaseHTMLProcessor):
  1836.     relative_uris = [
  1837.         ('a', 'href'),
  1838.         ('applet', 'codebase'),
  1839.         ('area', 'href'),
  1840.         ('blockquote', 'cite'),
  1841.         ('body', 'background'),
  1842.         ('del', 'cite'),
  1843.         ('form', 'action'),
  1844.         ('frame', 'longdesc'),
  1845.         ('frame', 'src'),
  1846.         ('iframe', 'longdesc'),
  1847.         ('iframe', 'src'),
  1848.         ('head', 'profile'),
  1849.         ('img', 'longdesc'),
  1850.         ('img', 'src'),
  1851.         ('img', 'usemap'),
  1852.         ('input', 'src'),
  1853.         ('input', 'usemap'),
  1854.         ('ins', 'cite'),
  1855.         ('link', 'href'),
  1856.         ('object', 'classid'),
  1857.         ('object', 'codebase'),
  1858.         ('object', 'data'),
  1859.         ('object', 'usemap'),
  1860.         ('q', 'cite'),
  1861.         ('script', 'src')]
  1862.     
  1863.     def __init__(self, baseuri, encoding):
  1864.         _BaseHTMLProcessor.__init__(self, encoding)
  1865.         self.baseuri = baseuri
  1866.  
  1867.     
  1868.     def resolveURI(self, uri):
  1869.         return _urljoin(self.baseuri, uri)
  1870.  
  1871.     
  1872.     def unknown_starttag(self, tag, attrs):
  1873.         attrs = self.normalize_attrs(attrs)
  1874.         attrs = [ (key, value) for key, value in attrs ]
  1875.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  1876.  
  1877.  
  1878.  
  1879. def _resolveRelativeURIs(htmlSource, baseURI, encoding):
  1880.     if _debug:
  1881.         sys.stderr.write('entering _resolveRelativeURIs\n')
  1882.     
  1883.     p = _RelativeURIResolver(baseURI, encoding)
  1884.     p.feed(htmlSource)
  1885.     return p.output()
  1886.  
  1887.  
  1888. class _HTMLSanitizer(_BaseHTMLProcessor):
  1889.     acceptable_elements = [
  1890.         'a',
  1891.         'abbr',
  1892.         'acronym',
  1893.         'address',
  1894.         'area',
  1895.         'b',
  1896.         'big',
  1897.         'blockquote',
  1898.         'br',
  1899.         'button',
  1900.         'caption',
  1901.         'center',
  1902.         'cite',
  1903.         'code',
  1904.         'col',
  1905.         'colgroup',
  1906.         'dd',
  1907.         'del',
  1908.         'dfn',
  1909.         'dir',
  1910.         'div',
  1911.         'dl',
  1912.         'dt',
  1913.         'em',
  1914.         'fieldset',
  1915.         'font',
  1916.         'form',
  1917.         'h1',
  1918.         'h2',
  1919.         'h3',
  1920.         'h4',
  1921.         'h5',
  1922.         'h6',
  1923.         'hr',
  1924.         'i',
  1925.         'img',
  1926.         'input',
  1927.         'ins',
  1928.         'kbd',
  1929.         'label',
  1930.         'legend',
  1931.         'li',
  1932.         'map',
  1933.         'menu',
  1934.         'ol',
  1935.         'optgroup',
  1936.         'option',
  1937.         'p',
  1938.         'pre',
  1939.         'q',
  1940.         's',
  1941.         'samp',
  1942.         'select',
  1943.         'small',
  1944.         'span',
  1945.         'strike',
  1946.         'strong',
  1947.         'sub',
  1948.         'sup',
  1949.         'table',
  1950.         'tbody',
  1951.         'td',
  1952.         'textarea',
  1953.         'tfoot',
  1954.         'th',
  1955.         'thead',
  1956.         'tr',
  1957.         'tt',
  1958.         'u',
  1959.         'ul',
  1960.         'var']
  1961.     acceptable_attributes = [
  1962.         'abbr',
  1963.         'accept',
  1964.         'accept-charset',
  1965.         'accesskey',
  1966.         'action',
  1967.         'align',
  1968.         'alt',
  1969.         'axis',
  1970.         'border',
  1971.         'cellpadding',
  1972.         'cellspacing',
  1973.         'char',
  1974.         'charoff',
  1975.         'charset',
  1976.         'checked',
  1977.         'cite',
  1978.         'class',
  1979.         'clear',
  1980.         'cols',
  1981.         'colspan',
  1982.         'color',
  1983.         'compact',
  1984.         'coords',
  1985.         'datetime',
  1986.         'dir',
  1987.         'disabled',
  1988.         'enctype',
  1989.         'for',
  1990.         'frame',
  1991.         'headers',
  1992.         'height',
  1993.         'href',
  1994.         'hreflang',
  1995.         'hspace',
  1996.         'id',
  1997.         'ismap',
  1998.         'label',
  1999.         'lang',
  2000.         'longdesc',
  2001.         'maxlength',
  2002.         'media',
  2003.         'method',
  2004.         'multiple',
  2005.         'name',
  2006.         'nohref',
  2007.         'noshade',
  2008.         'nowrap',
  2009.         'prompt',
  2010.         'readonly',
  2011.         'rel',
  2012.         'rev',
  2013.         'rows',
  2014.         'rowspan',
  2015.         'rules',
  2016.         'scope',
  2017.         'selected',
  2018.         'shape',
  2019.         'size',
  2020.         'span',
  2021.         'src',
  2022.         'start',
  2023.         'summary',
  2024.         'tabindex',
  2025.         'target',
  2026.         'title',
  2027.         'type',
  2028.         'usemap',
  2029.         'valign',
  2030.         'value',
  2031.         'vspace',
  2032.         'width']
  2033.     unacceptable_elements_with_end_tag = [
  2034.         'script',
  2035.         'applet']
  2036.     
  2037.     def reset(self):
  2038.         _BaseHTMLProcessor.reset(self)
  2039.         self.unacceptablestack = 0
  2040.  
  2041.     
  2042.     def unknown_starttag(self, tag, attrs):
  2043.         if tag not in self.acceptable_elements:
  2044.             if tag in self.unacceptable_elements_with_end_tag:
  2045.                 self.unacceptablestack += 1
  2046.             
  2047.             return None
  2048.         
  2049.         attrs = self.normalize_attrs(attrs)
  2050.         attrs = _[1]
  2051.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  2052.  
  2053.     
  2054.     def unknown_endtag(self, tag):
  2055.         if tag not in self.acceptable_elements:
  2056.             if tag in self.unacceptable_elements_with_end_tag:
  2057.                 self.unacceptablestack -= 1
  2058.             
  2059.             return None
  2060.         
  2061.         _BaseHTMLProcessor.unknown_endtag(self, tag)
  2062.  
  2063.     
  2064.     def handle_pi(self, text):
  2065.         pass
  2066.  
  2067.     
  2068.     def handle_decl(self, text):
  2069.         pass
  2070.  
  2071.     
  2072.     def handle_data(self, text):
  2073.         if not self.unacceptablestack:
  2074.             _BaseHTMLProcessor.handle_data(self, text)
  2075.         
  2076.  
  2077.  
  2078.  
  2079. def _sanitizeHTML(htmlSource, encoding):
  2080.     p = _HTMLSanitizer(encoding)
  2081.     p.feed(htmlSource)
  2082.     data = p.output()
  2083.     if TIDY_MARKUP:
  2084.         _tidy = None
  2085.         for tidy_interface in PREFERRED_TIDY_INTERFACES:
  2086.             
  2087.             try:
  2088.                 if tidy_interface == 'uTidy':
  2089.                     _utidy = parseString
  2090.                     import tidy
  2091.                     
  2092.                     def _tidy(data, **kwargs):
  2093.                         return str(_utidy(data, **kwargs))
  2094.  
  2095.                     break
  2096.                 elif tidy_interface == 'mxTidy':
  2097.                     _mxtidy = Tidy
  2098.                     import mx.Tidy
  2099.                     
  2100.                     def _tidy(data, **kwargs):
  2101.                         (nerrors, nwarnings, data, errordata) = _mxtidy.tidy(data, **kwargs)
  2102.                         return data
  2103.  
  2104.                     break
  2105.             continue
  2106.             continue
  2107.  
  2108.         
  2109.         if _tidy:
  2110.             utf8 = type(data) == type(u'')
  2111.             if utf8:
  2112.                 data = data.encode('utf-8')
  2113.             
  2114.             data = _tidy(data, output_xhtml = 1, numeric_entities = 1, wrap = 0, char_encoding = 'utf8')
  2115.             if utf8:
  2116.                 data = unicode(data, 'utf-8')
  2117.             
  2118.             if data.count('<body'):
  2119.                 data = data.split('<body', 1)[1]
  2120.                 if data.count('>'):
  2121.                     data = data.split('>', 1)[1]
  2122.                 
  2123.             
  2124.             if data.count('</body'):
  2125.                 data = data.split('</body', 1)[0]
  2126.             
  2127.         
  2128.     
  2129.     data = data.strip().replace('\r\n', '\n')
  2130.     return data
  2131.  
  2132.  
  2133. class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
  2134.     
  2135.     def http_error_default(self, req, fp, code, msg, headers):
  2136.         if code / 100 == 3 and code != 304:
  2137.             return self.http_error_302(req, fp, code, msg, headers)
  2138.         
  2139.         infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2140.         infourl.status = code
  2141.         return infourl
  2142.  
  2143.     
  2144.     def http_error_302(self, req, fp, code, msg, headers):
  2145.         if headers.dict.has_key('location'):
  2146.             infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
  2147.         else:
  2148.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2149.         if not hasattr(infourl, 'status'):
  2150.             infourl.status = code
  2151.         
  2152.         return infourl
  2153.  
  2154.     
  2155.     def http_error_301(self, req, fp, code, msg, headers):
  2156.         if headers.dict.has_key('location'):
  2157.             infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
  2158.         else:
  2159.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2160.         if not hasattr(infourl, 'status'):
  2161.             infourl.status = code
  2162.         
  2163.         return infourl
  2164.  
  2165.     http_error_300 = http_error_302
  2166.     http_error_303 = http_error_302
  2167.     http_error_307 = http_error_302
  2168.     
  2169.     def http_error_401(self, req, fp, code, msg, headers):
  2170.         host = urlparse.urlparse(req.get_full_url())[1]
  2171.         
  2172.         try:
  2173.             (user, passw) = base64.decodestring(req.headers['Authorization'].split(' ')[1]).split(':')
  2174.             realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
  2175.             self.add_password(realm, host, user, passw)
  2176.             retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
  2177.             self.reset_retry_count()
  2178.             return retry
  2179.         except:
  2180.             return self.http_error_default(req, fp, code, msg, headers)
  2181.  
  2182.  
  2183.  
  2184.  
  2185. def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
  2186.     if hasattr(url_file_stream_or_string, 'read'):
  2187.         return url_file_stream_or_string
  2188.     
  2189.     if url_file_stream_or_string == '-':
  2190.         return sys.stdin
  2191.     
  2192.     if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp'):
  2193.         if not agent:
  2194.             agent = USER_AGENT
  2195.         
  2196.         auth = None
  2197.         if base64:
  2198.             (urltype, rest) = urllib.splittype(url_file_stream_or_string)
  2199.             (realhost, rest) = urllib.splithost(rest)
  2200.             if realhost:
  2201.                 (user_passwd, realhost) = urllib.splituser(realhost)
  2202.                 if user_passwd:
  2203.                     url_file_stream_or_string = '%s://%s%s' % (urltype, realhost, rest)
  2204.                     auth = base64.encodestring(user_passwd).strip()
  2205.                 
  2206.             
  2207.         
  2208.         request = urllib2.Request(url_file_stream_or_string)
  2209.         request.add_header('User-Agent', agent)
  2210.         if etag:
  2211.             request.add_header('If-None-Match', etag)
  2212.         
  2213.         if modified:
  2214.             short_weekdays = [
  2215.                 'Mon',
  2216.                 'Tue',
  2217.                 'Wed',
  2218.                 'Thu',
  2219.                 'Fri',
  2220.                 'Sat',
  2221.                 'Sun']
  2222.             months = [
  2223.                 'Jan',
  2224.                 'Feb',
  2225.                 'Mar',
  2226.                 'Apr',
  2227.                 'May',
  2228.                 'Jun',
  2229.                 'Jul',
  2230.                 'Aug',
  2231.                 'Sep',
  2232.                 'Oct',
  2233.                 'Nov',
  2234.                 'Dec']
  2235.             request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5]))
  2236.         
  2237.         if referrer:
  2238.             request.add_header('Referer', referrer)
  2239.         
  2240.         if gzip and zlib:
  2241.             request.add_header('Accept-encoding', 'gzip, deflate')
  2242.         elif gzip:
  2243.             request.add_header('Accept-encoding', 'gzip')
  2244.         elif zlib:
  2245.             request.add_header('Accept-encoding', 'deflate')
  2246.         else:
  2247.             request.add_header('Accept-encoding', '')
  2248.         if auth:
  2249.             request.add_header('Authorization', 'Basic %s' % auth)
  2250.         
  2251.         if ACCEPT_HEADER:
  2252.             request.add_header('Accept', ACCEPT_HEADER)
  2253.         
  2254.         request.add_header('A-IM', 'feed')
  2255.         opener = apply(urllib2.build_opener, tuple([
  2256.             _FeedURLHandler()] + handlers))
  2257.         opener.addheaders = []
  2258.         
  2259.         try:
  2260.             return opener.open(request)
  2261.         finally:
  2262.             opener.close()
  2263.  
  2264.     
  2265.     
  2266.     try:
  2267.         return open(url_file_stream_or_string)
  2268.     except:
  2269.         pass
  2270.  
  2271.     return _StringIO(str(url_file_stream_or_string))
  2272.  
  2273. _date_handlers = []
  2274.  
  2275. def registerDateHandler(func):
  2276.     _date_handlers.insert(0, func)
  2277.  
  2278. _iso8601_tmpl = [
  2279.     'YYYY-?MM-?DD',
  2280.     'YYYY-MM',
  2281.     'YYYY-?OOO',
  2282.     'YY-?MM-?DD',
  2283.     'YY-?OOO',
  2284.     'YYYY',
  2285.     '-YY-?MM',
  2286.     '-OOO',
  2287.     '-YY',
  2288.     '--MM-?DD',
  2289.     '--MM',
  2290.     '---DD',
  2291.     'CC',
  2292.     '']
  2293. _iso8601_re = [ tmpl.replace('YYYY', '(?P<year>\\d{4})').replace('YY', '(?P<year>\\d\\d)').replace('MM', '(?P<month>[01]\\d)').replace('DD', '(?P<day>[0123]\\d)').replace('OOO', '(?P<ordinal>[0123]\\d\\d)').replace('CC', '(?P<century>\\d\\d$)') + '(T?(?P<hour>\\d{2}):(?P<minute>\\d{2})' + '(:(?P<second>\\d{2}))?' + '(?P<tz>[+-](?P<tzhour>\\d{2})(:(?P<tzmin>\\d{2}))?|Z)?)?' for tmpl in _iso8601_tmpl ]
  2294. del tmpl
  2295. _iso8601_matches = [ re.compile(regex).match for regex in _iso8601_re ]
  2296. del regex
  2297.  
  2298. def _parse_date_iso8601(dateString):
  2299.     m = None
  2300.     for _iso8601_match in _iso8601_matches:
  2301.         m = _iso8601_match(dateString)
  2302.         if m:
  2303.             break
  2304.             continue
  2305.     
  2306.     if not m:
  2307.         return None
  2308.     
  2309.     if m.span() == (0, 0):
  2310.         return None
  2311.     
  2312.     params = m.groupdict()
  2313.     ordinal = params.get('ordinal', 0)
  2314.     if ordinal:
  2315.         ordinal = int(ordinal)
  2316.     else:
  2317.         ordinal = 0
  2318.     year = params.get('year', '--')
  2319.     if not year or year == '--':
  2320.         year = time.gmtime()[0]
  2321.     elif len(year) == 2:
  2322.         year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2323.     else:
  2324.         year = int(year)
  2325.     month = params.get('month', '-')
  2326.     if not month or month == '-':
  2327.         if ordinal:
  2328.             month = 1
  2329.         else:
  2330.             month = time.gmtime()[1]
  2331.     
  2332.     month = int(month)
  2333.     day = params.get('day', 0)
  2334.     if not day:
  2335.         if ordinal:
  2336.             day = ordinal
  2337.         elif params.get('century', 0) and params.get('year', 0) or params.get('month', 0):
  2338.             day = 1
  2339.         else:
  2340.             day = time.gmtime()[2]
  2341.     else:
  2342.         day = int(day)
  2343.     if 'century' in params.keys():
  2344.         year = (int(params['century']) - 1) * 100 + 1
  2345.     
  2346.     for field in [
  2347.         'hour',
  2348.         'minute',
  2349.         'second',
  2350.         'tzhour',
  2351.         'tzmin']:
  2352.         if not params.get(field, None):
  2353.             params[field] = 0
  2354.             continue
  2355.     
  2356.     hour = int(params.get('hour', 0))
  2357.     minute = int(params.get('minute', 0))
  2358.     second = int(params.get('second', 0))
  2359.     weekday = 0
  2360.     daylight_savings_flag = 0
  2361.     tm = [
  2362.         year,
  2363.         month,
  2364.         day,
  2365.         hour,
  2366.         minute,
  2367.         second,
  2368.         weekday,
  2369.         ordinal,
  2370.         daylight_savings_flag]
  2371.     tz = params.get('tz')
  2372.     if tz and tz != 'Z':
  2373.         if tz[0] == '-':
  2374.             tm[3] += int(params.get('tzhour', 0))
  2375.             tm[4] += int(params.get('tzmin', 0))
  2376.         elif tz[0] == '+':
  2377.             tm[3] -= int(params.get('tzhour', 0))
  2378.             tm[4] -= int(params.get('tzmin', 0))
  2379.         else:
  2380.             return None
  2381.     
  2382.     return time.localtime(time.mktime(tm))
  2383.  
  2384. registerDateHandler(_parse_date_iso8601)
  2385. _korean_year = u'δàä'
  2386. _korean_month = u'∞¢ö'
  2387. _korean_day = u'∞¥╝'
  2388. _korean_am = u'∞ÿñ∞áä'
  2389. _korean_pm = u'∞ÿñφ¢ä'
  2390. _korean_onblog_date_re = re.compile('(\\d{4})%s\\s+(\\d{2})%s\\s+(\\d{2})%s\\s+(\\d{2}):(\\d{2}):(\\d{2})' % (_korean_year, _korean_month, _korean_day))
  2391. _korean_nate_date_re = re.compile(u'(\\d{4})-(\\d{2})-(\\d{2})\\s+(%s|%s)\\s+(\\d{,2}):(\\d{,2}):(\\d{,2})' % (_korean_am, _korean_pm))
  2392.  
  2393. def _parse_date_onblog(dateString):
  2394.     m = _korean_onblog_date_re.match(dateString)
  2395.     if not m:
  2396.         return None
  2397.     
  2398.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2399.         'year': m.group(1),
  2400.         'month': m.group(2),
  2401.         'day': m.group(3),
  2402.         'hour': m.group(4),
  2403.         'minute': m.group(5),
  2404.         'second': m.group(6),
  2405.         'zonediff': '+09:00' }
  2406.     if _debug:
  2407.         sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate)
  2408.     
  2409.     return _parse_date_w3dtf(w3dtfdate)
  2410.  
  2411. registerDateHandler(_parse_date_onblog)
  2412.  
  2413. def _parse_date_nate(dateString):
  2414.     m = _korean_nate_date_re.match(dateString)
  2415.     if not m:
  2416.         return None
  2417.     
  2418.     hour = int(m.group(5))
  2419.     ampm = m.group(4)
  2420.     if ampm == _korean_pm:
  2421.         hour += 12
  2422.     
  2423.     hour = str(hour)
  2424.     if len(hour) == 1:
  2425.         hour = '0' + hour
  2426.     
  2427.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2428.         'year': m.group(1),
  2429.         'month': m.group(2),
  2430.         'day': m.group(3),
  2431.         'hour': hour,
  2432.         'minute': m.group(6),
  2433.         'second': m.group(7),
  2434.         'zonediff': '+09:00' }
  2435.     if _debug:
  2436.         sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate)
  2437.     
  2438.     return _parse_date_w3dtf(w3dtfdate)
  2439.  
  2440. registerDateHandler(_parse_date_nate)
  2441. _mssql_date_re = re.compile('(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?')
  2442.  
  2443. def _parse_date_mssql(dateString):
  2444.     m = _mssql_date_re.match(dateString)
  2445.     if not m:
  2446.         return None
  2447.     
  2448.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2449.         'year': m.group(1),
  2450.         'month': m.group(2),
  2451.         'day': m.group(3),
  2452.         'hour': m.group(4),
  2453.         'minute': m.group(5),
  2454.         'second': m.group(6),
  2455.         'zonediff': '+09:00' }
  2456.     if _debug:
  2457.         sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate)
  2458.     
  2459.     return _parse_date_w3dtf(w3dtfdate)
  2460.  
  2461. registerDateHandler(_parse_date_mssql)
  2462. _greek_months = {
  2463.     u'╬Ö╬▒╬╜': u'Jan',
  2464.     u'╬ª╬╡╬▓': u'Feb',
  2465.     u'╬£╬¼╧Ä': u'Mar',
  2466.     u'╬£╬▒╧Ä': u'Mar',
  2467.     u'╬æ╧Ç╧ü': u'Apr',
  2468.     u'╬£╬¼╬╣': u'May',
  2469.     u'╬£╬▒╧è': u'May',
  2470.     u'╬£╬▒╬╣': u'May',
  2471.     u'╬Ö╬┐╧ì╬╜': u'Jun',
  2472.     u'╬Ö╬┐╬╜': u'Jun',
  2473.     u'╬Ö╬┐╧ì╬╗': u'Jul',
  2474.     u'╬Ö╬┐╬╗': u'Jul',
  2475.     u'╬æ╧ì╬│': u'Aug',
  2476.     u'╬æ╧à╬│': u'Aug',
  2477.     u'╬ú╬╡╧Ç': u'Sep',
  2478.     u'╬ƒ╬║╧ä': u'Oct',
  2479.     u'╬¥╬┐╬¡': u'Nov',
  2480.     u'╬¥╬┐╬╡': u'Nov',
  2481.     u'╬ö╬╡╬║': u'Dec' }
  2482. _greek_wdays = {
  2483.     u'╬Ü╧à╧ü': u'Sun',
  2484.     u'╬ö╬╡╧à': u'Mon',
  2485.     u'╬ñ╧ü╬╣': u'Tue',
  2486.     u'╬ñ╬╡╧ä': u'Wed',
  2487.     u'╬á╬╡╬╝': u'Thu',
  2488.     u'╬á╬▒╧ü': u'Fri',
  2489.     u'╬ú╬▒╬▓': u'Sat' }
  2490. _greek_date_format_re = re.compile(u'([^,]+),\\s+(\\d{2})\\s+([^\\s]+)\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+([^\\s]+)')
  2491.  
  2492. def _parse_date_greek(dateString):
  2493.     m = _greek_date_format_re.match(dateString)
  2494.     if not m:
  2495.         return None
  2496.     
  2497.     
  2498.     try:
  2499.         wday = _greek_wdays[m.group(1)]
  2500.         month = _greek_months[m.group(3)]
  2501.     except:
  2502.         return None
  2503.  
  2504.     rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % {
  2505.         'wday': wday,
  2506.         'day': m.group(2),
  2507.         'month': month,
  2508.         'year': m.group(4),
  2509.         'hour': m.group(5),
  2510.         'minute': m.group(6),
  2511.         'second': m.group(7),
  2512.         'zonediff': m.group(8) }
  2513.     if _debug:
  2514.         sys.stderr.write('Greek date parsed as: %s\n' % rfc822date)
  2515.     
  2516.     return _parse_date_rfc822(rfc822date)
  2517.  
  2518. registerDateHandler(_parse_date_greek)
  2519. _hungarian_months = {
  2520.     u'janu├ír': u'01',
  2521.     u'febru├íri': u'02',
  2522.     u'm├írcius': u'03',
  2523.     u'├íprilis': u'04',
  2524.     u'm├íujus': u'05',
  2525.     u'j├║nius': u'06',
  2526.     u'j├║lius': u'07',
  2527.     u'augusztus': u'08',
  2528.     u'szeptember': u'09',
  2529.     u'okt├│ber': u'10',
  2530.     u'november': u'11',
  2531.     u'december': u'12' }
  2532. _hungarian_date_format_re = re.compile(u'(\\d{4})-([^-]+)-(\\d{,2})T(\\d{,2}):(\\d{2})((\\+|-)(\\d{,2}:\\d{2}))')
  2533.  
  2534. def _parse_date_hungarian(dateString):
  2535.     m = _hungarian_date_format_re.match(dateString)
  2536.     if not m:
  2537.         return None
  2538.     
  2539.     
  2540.     try:
  2541.         month = _hungarian_months[m.group(2)]
  2542.         day = m.group(3)
  2543.         if len(day) == 1:
  2544.             day = '0' + day
  2545.         
  2546.         hour = m.group(4)
  2547.         if len(hour) == 1:
  2548.             hour = '0' + hour
  2549.     except:
  2550.         return None
  2551.  
  2552.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % {
  2553.         'year': m.group(1),
  2554.         'month': month,
  2555.         'day': day,
  2556.         'hour': hour,
  2557.         'minute': m.group(5),
  2558.         'zonediff': m.group(6) }
  2559.     if _debug:
  2560.         sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate)
  2561.     
  2562.     return _parse_date_w3dtf(w3dtfdate)
  2563.  
  2564. registerDateHandler(_parse_date_hungarian)
  2565.  
  2566. def _parse_date_w3dtf(dateString):
  2567.     
  2568.     def __extract_date(m):
  2569.         year = int(m.group('year'))
  2570.         if year < 100:
  2571.             year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2572.         
  2573.         if year < 1000:
  2574.             return (0, 0, 0)
  2575.         
  2576.         julian = m.group('julian')
  2577.         if julian:
  2578.             julian = int(julian)
  2579.             month = julian / 30 + 1
  2580.             day = julian % 30 + 1
  2581.             jday = None
  2582.             while jday != julian:
  2583.                 t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
  2584.                 jday = time.gmtime(t)[-2]
  2585.                 diff = abs(jday - julian)
  2586.                 if jday > julian:
  2587.                     if diff < day:
  2588.                         day = day - diff
  2589.                     else:
  2590.                         month = month - 1
  2591.                         day = 31
  2592.                 diff < day
  2593.                 if jday < julian:
  2594.                     if day + diff < 28:
  2595.                         day = day + diff
  2596.                     else:
  2597.                         month = month + 1
  2598.                 day + diff < 28
  2599.             return (year, month, day)
  2600.         
  2601.         month = m.group('month')
  2602.         day = 1
  2603.         if month is None:
  2604.             month = 1
  2605.         else:
  2606.             month = int(month)
  2607.             day = m.group('day')
  2608.             if day:
  2609.                 day = int(day)
  2610.             else:
  2611.                 day = 1
  2612.         return (year, month, day)
  2613.  
  2614.     
  2615.     def __extract_time(m):
  2616.         if not m:
  2617.             return (0, 0, 0)
  2618.         
  2619.         hours = m.group('hours')
  2620.         if not hours:
  2621.             return (0, 0, 0)
  2622.         
  2623.         hours = int(hours)
  2624.         minutes = int(m.group('minutes'))
  2625.         seconds = m.group('seconds')
  2626.         if seconds:
  2627.             seconds = int(seconds)
  2628.         else:
  2629.             seconds = 0
  2630.         return (hours, minutes, seconds)
  2631.  
  2632.     
  2633.     def __extract_tzd(m):
  2634.         if not m:
  2635.             return 0
  2636.         
  2637.         tzd = m.group('tzd')
  2638.         if not tzd:
  2639.             return 0
  2640.         
  2641.         if tzd == 'Z':
  2642.             return 0
  2643.         
  2644.         hours = int(m.group('tzdhours'))
  2645.         minutes = m.group('tzdminutes')
  2646.         if minutes:
  2647.             minutes = int(minutes)
  2648.         else:
  2649.             minutes = 0
  2650.         offset = (hours * 60 + minutes) * 60
  2651.         if tzd[0] == '+':
  2652.             return -offset
  2653.         
  2654.         return offset
  2655.  
  2656.     __date_re = '(?P<year>\\d\\d\\d\\d)(?:(?P<dsep>-|)(?:(?P<julian>\\d\\d\\d)|(?P<month>\\d\\d)(?:(?P=dsep)(?P<day>\\d\\d))?))?'
  2657.     __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\\d\\d)(?::?(?P<tzdminutes>\\d\\d))|Z)'
  2658.     __tzd_rx = re.compile(__tzd_re)
  2659.     __time_re = '(?P<hours>\\d\\d)(?P<tsep>:|)(?P<minutes>\\d\\d)(?:(?P=tsep)(?P<seconds>\\d\\d(?:[.,]\\d+)?))?' + __tzd_re
  2660.     __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
  2661.     __datetime_rx = re.compile(__datetime_re)
  2662.     m = __datetime_rx.match(dateString)
  2663.     if m is None or m.group() != dateString:
  2664.         return None
  2665.     
  2666.     gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
  2667.     if gmt[0] == 0:
  2668.         return None
  2669.     
  2670.     return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
  2671.  
  2672. registerDateHandler(_parse_date_w3dtf)
  2673.  
  2674. def _parse_date_rfc822(dateString):
  2675.     data = dateString.split()
  2676.     if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
  2677.         del data[0]
  2678.     
  2679.     if len(data) == 4:
  2680.         s = data[3]
  2681.         i = s.find('+')
  2682.         if i > 0:
  2683.             data[3:] = [
  2684.                 s[:i],
  2685.                 s[i + 1:]]
  2686.         else:
  2687.             data.append('')
  2688.         dateString = ' '.join(data)
  2689.     
  2690.     if len(data) < 5:
  2691.         dateString += ' 00:00:00 GMT'
  2692.     
  2693.     tm = rfc822.parsedate_tz(dateString)
  2694.     if tm:
  2695.         return time.gmtime(rfc822.mktime_tz(tm))
  2696.     
  2697.  
  2698. _additional_timezones = {
  2699.     'AT': -400,
  2700.     'ET': -500,
  2701.     'CT': -600,
  2702.     'MT': -700,
  2703.     'PT': -800 }
  2704. rfc822._timezones.update(_additional_timezones)
  2705. registerDateHandler(_parse_date_rfc822)
  2706.  
  2707. def _parse_date(dateString):
  2708.     for handler in _date_handlers:
  2709.         
  2710.         try:
  2711.             date9tuple = handler(dateString)
  2712.             if not date9tuple:
  2713.                 continue
  2714.             
  2715.             if len(date9tuple) != 9:
  2716.                 if _debug:
  2717.                     sys.stderr.write('date handler function must return 9-tuple\n')
  2718.                 
  2719.                 raise ValueError
  2720.             
  2721.             map(int, date9tuple)
  2722.             return date9tuple
  2723.         continue
  2724.         except Exception:
  2725.             e = None
  2726.             if _debug:
  2727.                 sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e)))
  2728.             
  2729.             _debug
  2730.         
  2731.  
  2732.     
  2733.  
  2734.  
  2735. def _getCharacterEncoding(http_headers, xml_data):
  2736.     
  2737.     def _parseHTTPContentType(content_type):
  2738.         if not content_type:
  2739.             pass
  2740.         content_type = ''
  2741.         (content_type, params) = cgi.parse_header(content_type)
  2742.         return (content_type, params.get('charset', '').replace("'", ''))
  2743.  
  2744.     sniffed_xml_encoding = ''
  2745.     xml_encoding = ''
  2746.     true_encoding = ''
  2747.     (http_content_type, http_encoding) = _parseHTTPContentType(http_headers.get('content-type'))
  2748.     
  2749.     try:
  2750.         if xml_data[:4] == 'Lo\xa7\x94':
  2751.             xml_data = _ebcdic_to_ascii(xml_data)
  2752.         elif xml_data[:4] == '\x00<\x00?':
  2753.             sniffed_xml_encoding = 'utf-16be'
  2754.             xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
  2755.         elif len(xml_data) >= 4 and xml_data[:2] == '\xfe\xff' and xml_data[2:4] != '\x00\x00':
  2756.             sniffed_xml_encoding = 'utf-16be'
  2757.             xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
  2758.         elif xml_data[:4] == '<\x00?\x00':
  2759.             sniffed_xml_encoding = 'utf-16le'
  2760.             xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
  2761.         elif len(xml_data) >= 4 and xml_data[:2] == '\xff\xfe' and xml_data[2:4] != '\x00\x00':
  2762.             sniffed_xml_encoding = 'utf-16le'
  2763.             xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
  2764.         elif xml_data[:4] == '\x00\x00\x00<':
  2765.             sniffed_xml_encoding = 'utf-32be'
  2766.             xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
  2767.         elif xml_data[:4] == '<\x00\x00\x00':
  2768.             sniffed_xml_encoding = 'utf-32le'
  2769.             xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
  2770.         elif xml_data[:4] == '\x00\x00\xfe\xff':
  2771.             sniffed_xml_encoding = 'utf-32be'
  2772.             xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
  2773.         elif xml_data[:4] == '\xff\xfe\x00\x00':
  2774.             sniffed_xml_encoding = 'utf-32le'
  2775.             xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
  2776.         elif xml_data[:3] == '\xef\xbb\xbf':
  2777.             sniffed_xml_encoding = 'utf-8'
  2778.             xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
  2779.         
  2780.         xml_encoding_match = re.compile('^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>').match(xml_data)
  2781.     except:
  2782.         xml_encoding_match = None
  2783.  
  2784.     if xml_encoding_match:
  2785.         xml_encoding = xml_encoding_match.groups()[0].lower()
  2786.         if sniffed_xml_encoding and xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16'):
  2787.             xml_encoding = sniffed_xml_encoding
  2788.         
  2789.     
  2790.     acceptable_content_type = 0
  2791.     application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
  2792.     text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
  2793.     if (http_content_type in application_content_types or http_content_type.startswith('application/')) and http_content_type.endswith('+xml'):
  2794.         acceptable_content_type = 1
  2795.         if not http_encoding and xml_encoding:
  2796.             pass
  2797.         true_encoding = 'utf-8'
  2798.     elif (http_content_type in text_content_types or http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
  2799.         acceptable_content_type = 1
  2800.         if not http_encoding:
  2801.             pass
  2802.         true_encoding = 'us-ascii'
  2803.     elif http_content_type.startswith('text/'):
  2804.         if not http_encoding:
  2805.             pass
  2806.         true_encoding = 'us-ascii'
  2807.     elif http_headers and not http_headers.has_key('content-type'):
  2808.         if not xml_encoding:
  2809.             pass
  2810.         true_encoding = 'iso-8859-1'
  2811.     elif not xml_encoding:
  2812.         pass
  2813.     true_encoding = 'utf-8'
  2814.     return (true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type)
  2815.  
  2816.  
  2817. def _toUTF8(data, encoding):
  2818.     if _debug:
  2819.         sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding)
  2820.     
  2821.     if len(data) >= 4 and data[:2] == '\xfe\xff' and data[2:4] != '\x00\x00':
  2822.         if _debug:
  2823.             sys.stderr.write('stripping BOM\n')
  2824.             if encoding != 'utf-16be':
  2825.                 sys.stderr.write('trying utf-16be instead\n')
  2826.             
  2827.         
  2828.         encoding = 'utf-16be'
  2829.         data = data[2:]
  2830.     elif len(data) >= 4 and data[:2] == '\xff\xfe' and data[2:4] != '\x00\x00':
  2831.         if _debug:
  2832.             sys.stderr.write('stripping BOM\n')
  2833.             if encoding != 'utf-16le':
  2834.                 sys.stderr.write('trying utf-16le instead\n')
  2835.             
  2836.         
  2837.         encoding = 'utf-16le'
  2838.         data = data[2:]
  2839.     elif data[:3] == '\xef\xbb\xbf':
  2840.         if _debug:
  2841.             sys.stderr.write('stripping BOM\n')
  2842.             if encoding != 'utf-8':
  2843.                 sys.stderr.write('trying utf-8 instead\n')
  2844.             
  2845.         
  2846.         encoding = 'utf-8'
  2847.         data = data[3:]
  2848.     elif data[:4] == '\x00\x00\xfe\xff':
  2849.         if _debug:
  2850.             sys.stderr.write('stripping BOM\n')
  2851.             if encoding != 'utf-32be':
  2852.                 sys.stderr.write('trying utf-32be instead\n')
  2853.             
  2854.         
  2855.         encoding = 'utf-32be'
  2856.         data = data[4:]
  2857.     elif data[:4] == '\xff\xfe\x00\x00':
  2858.         if _debug:
  2859.             sys.stderr.write('stripping BOM\n')
  2860.             if encoding != 'utf-32le':
  2861.                 sys.stderr.write('trying utf-32le instead\n')
  2862.             
  2863.         
  2864.         encoding = 'utf-32le'
  2865.         data = data[4:]
  2866.     
  2867.     newdata = unicode(data, encoding)
  2868.     if _debug:
  2869.         sys.stderr.write('successfully converted %s data to unicode\n' % encoding)
  2870.     
  2871.     declmatch = re.compile('^<\\?xml[^>]*?>')
  2872.     newdecl = "<?xml version='1.0' encoding='utf-8'?>"
  2873.     if declmatch.search(newdata):
  2874.         newdata = declmatch.sub(newdecl, newdata)
  2875.     else:
  2876.         newdata = newdecl + u'\n' + newdata
  2877.     return newdata.encode('utf-8')
  2878.  
  2879.  
  2880. def _stripDoctype(data):
  2881.     entity_pattern = re.compile('<!ENTITY([^>]*?)>', re.MULTILINE)
  2882.     data = entity_pattern.sub('', data)
  2883.     doctype_pattern = re.compile('<!DOCTYPE([^>]*?)>', re.MULTILINE)
  2884.     doctype_results = doctype_pattern.findall(data)
  2885.     if not doctype_results or doctype_results[0]:
  2886.         pass
  2887.     doctype = ''
  2888.     if doctype.lower().count('netscape'):
  2889.         version = 'rss091n'
  2890.     else:
  2891.         version = None
  2892.     data = doctype_pattern.sub('', data)
  2893.     return (version, data)
  2894.  
  2895.  
  2896. def parse(url_file_stream_or_string, etag = None, modified = None, agent = None, referrer = None, handlers = []):
  2897.     result = FeedParserDict()
  2898.     result['feed'] = FeedParserDict()
  2899.     result['entries'] = []
  2900.     if _XML_AVAILABLE:
  2901.         result['bozo'] = 0
  2902.     
  2903.     if type(handlers) == types.InstanceType:
  2904.         handlers = [
  2905.             handlers]
  2906.     
  2907.     
  2908.     try:
  2909.         f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers)
  2910.         data = f.read()
  2911.     except Exception:
  2912.         e = None
  2913.         result['bozo'] = 1
  2914.         result['bozo_exception'] = e
  2915.         data = ''
  2916.         f = None
  2917.  
  2918.     if f and data and hasattr(f, 'headers'):
  2919.         if gzip and f.headers.get('content-encoding', '') == 'gzip':
  2920.             
  2921.             try:
  2922.                 data = gzip.GzipFile(fileobj = _StringIO(data)).read()
  2923.             except Exception:
  2924.                 e = None
  2925.                 result['bozo'] = 1
  2926.                 result['bozo_exception'] = e
  2927.                 data = ''
  2928.             except:
  2929.                 None<EXCEPTION MATCH>Exception
  2930.             
  2931.  
  2932.         None<EXCEPTION MATCH>Exception
  2933.         if zlib and f.headers.get('content-encoding', '') == 'deflate':
  2934.             
  2935.             try:
  2936.                 data = zlib.decompress(data, -(zlib.MAX_WBITS))
  2937.             except Exception:
  2938.                 e = None
  2939.                 result['bozo'] = 1
  2940.                 result['bozo_exception'] = e
  2941.                 data = ''
  2942.             except:
  2943.                 None<EXCEPTION MATCH>Exception
  2944.             
  2945.  
  2946.         None<EXCEPTION MATCH>Exception
  2947.     
  2948.     if hasattr(f, 'info'):
  2949.         info = f.info()
  2950.         result['etag'] = info.getheader('ETag')
  2951.         last_modified = info.getheader('Last-Modified')
  2952.         if last_modified:
  2953.             result['modified'] = _parse_date(last_modified)
  2954.         
  2955.     
  2956.     if hasattr(f, 'url'):
  2957.         result['href'] = f.url
  2958.         result['status'] = 200
  2959.     
  2960.     if hasattr(f, 'status'):
  2961.         result['status'] = f.status
  2962.     
  2963.     if hasattr(f, 'headers'):
  2964.         result['headers'] = f.headers.dict
  2965.     
  2966.     if hasattr(f, 'close'):
  2967.         f.close()
  2968.     
  2969.     http_headers = result.get('headers', { })
  2970.     (result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type) = _getCharacterEncoding(http_headers, data)
  2971.     if http_headers and not acceptable_content_type:
  2972.         if http_headers.has_key('content-type'):
  2973.             bozo_message = '%s is not an XML media type' % http_headers['content-type']
  2974.         else:
  2975.             bozo_message = 'no Content-type specified'
  2976.         result['bozo'] = 1
  2977.         result['bozo_exception'] = NonXMLContentType(bozo_message)
  2978.     
  2979.     (result['version'], data) = _stripDoctype(data)
  2980.     baseuri = http_headers.get('content-location', result.get('href'))
  2981.     baselang = http_headers.get('content-language', None)
  2982.     if result.get('status', 0) == 304:
  2983.         result['version'] = ''
  2984.         result['debug_message'] = 'The feed has not changed since you last checked, ' + 'so the server sent no data.  This is a feature, not a bug!'
  2985.         return result
  2986.     
  2987.     if not data:
  2988.         return result
  2989.     
  2990.     use_strict_parser = 0
  2991.     known_encoding = 0
  2992.     tried_encodings = []
  2993.     for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding):
  2994.         if not proposed_encoding:
  2995.             continue
  2996.         
  2997.         if proposed_encoding in tried_encodings:
  2998.             continue
  2999.         
  3000.         tried_encodings.append(proposed_encoding)
  3001.         
  3002.         try:
  3003.             data = _toUTF8(data, proposed_encoding)
  3004.             known_encoding = use_strict_parser = 1
  3005.         continue
  3006.         continue
  3007.  
  3008.     
  3009.     if not known_encoding and chardet:
  3010.         
  3011.         try:
  3012.             proposed_encoding = chardet.detect(data)['encoding']
  3013.             if proposed_encoding and proposed_encoding not in tried_encodings:
  3014.                 tried_encodings.append(proposed_encoding)
  3015.                 data = _toUTF8(data, proposed_encoding)
  3016.                 known_encoding = use_strict_parser = 1
  3017.  
  3018.     
  3019.     if not known_encoding and 'utf-8' not in tried_encodings:
  3020.         
  3021.         try:
  3022.             proposed_encoding = 'utf-8'
  3023.             tried_encodings.append(proposed_encoding)
  3024.             data = _toUTF8(data, proposed_encoding)
  3025.             known_encoding = use_strict_parser = 1
  3026.  
  3027.     
  3028.     if not known_encoding and 'windows-1252' not in tried_encodings:
  3029.         
  3030.         try:
  3031.             proposed_encoding = 'windows-1252'
  3032.             tried_encodings.append(proposed_encoding)
  3033.             data = _toUTF8(data, proposed_encoding)
  3034.             known_encoding = use_strict_parser = 1
  3035.  
  3036.     
  3037.     if not known_encoding:
  3038.         result['bozo'] = 1
  3039.         result['bozo_exception'] = CharacterEncodingUnknown('document encoding unknown, I tried ' + '%s, %s, utf-8, and windows-1252 but nothing worked' % (result['encoding'], xml_encoding))
  3040.         result['encoding'] = ''
  3041.     elif proposed_encoding != result['encoding']:
  3042.         result['bozo'] = 1
  3043.         result['bozo_exception'] = CharacterEncodingOverride('documented declared as %s, but parsed as %s' % (result['encoding'], proposed_encoding))
  3044.         result['encoding'] = proposed_encoding
  3045.     
  3046.     if not _XML_AVAILABLE:
  3047.         use_strict_parser = 0
  3048.     
  3049.     if use_strict_parser:
  3050.         feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
  3051.         saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
  3052.         saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
  3053.         saxparser.setContentHandler(feedparser)
  3054.         saxparser.setErrorHandler(feedparser)
  3055.         source = xml.sax.xmlreader.InputSource()
  3056.         source.setByteStream(_StringIO(data))
  3057.         if hasattr(saxparser, '_ns_stack'):
  3058.             saxparser._ns_stack.append({
  3059.                 'http://www.w3.org/XML/1998/namespace': 'xml' })
  3060.         
  3061.         
  3062.         try:
  3063.             saxparser.parse(source)
  3064.         except Exception:
  3065.             e = None
  3066.             if _debug:
  3067.                 import traceback
  3068.                 traceback.print_stack()
  3069.                 traceback.print_exc()
  3070.                 sys.stderr.write('xml parsing failed\n')
  3071.             
  3072.             result['bozo'] = 1
  3073.             if not feedparser.exc:
  3074.                 pass
  3075.             result['bozo_exception'] = e
  3076.             use_strict_parser = 0
  3077.         except:
  3078.             None<EXCEPTION MATCH>Exception
  3079.         
  3080.  
  3081.     None<EXCEPTION MATCH>Exception
  3082.     if not use_strict_parser:
  3083.         if not known_encoding or 'utf-8':
  3084.             pass
  3085.         feedparser = _LooseFeedParser(baseuri, baselang, '')
  3086.         feedparser.feed(data)
  3087.     
  3088.     result['feed'] = feedparser.feeddata
  3089.     result['entries'] = feedparser.entries
  3090.     if not result['version']:
  3091.         pass
  3092.     result['version'] = feedparser.version
  3093.     result['namespaces'] = feedparser.namespacesInUse
  3094.     return result
  3095.  
  3096. if __name__ == '__main__':
  3097.     zopeCompatibilityHack()
  3098.     from pprint import pprint
  3099.     for url in urls:
  3100.         print url
  3101.         print 
  3102.         result = parse(url)
  3103.         pprint(result)
  3104.         print 
  3105.     
  3106.  
  3107.